I'm writing some test cases, and I've got a test case that is using Mock objects. I need to check to see if two class methods are called from another class method. Here's what I've done:
First I generated the Mock:
Mock::generate('Parser');
Then, inside my test I called:
$P = new MockParser();
$P->expectOnce('loadUrl', array('http://url'));
$P->expectOnce('parse');
$P->fetchAndParse('http://url');
My implementation code looks like:
public function fetchAndParse($url) {
$this->loadUrl($url);
$this->parse();
}
And the loadUrl and parse() methods definately exist. I'm getting two failures on my tests, both telling me "Expected call count for [loadUrl] was [1] got [0]". I've got no idea what's going on - the methods are being called from that function!
Thanks,
Jamie
While my experience has been with mocking frameworks in the .NET world, I think that what you're trying to do is incorrect.
Any mocking framework when asked to create a mock for a class, generates "stubs" for ALL the methods in that class. This includes the method fetchAndParse. So when you are calling fetchAndParse on your mock object $P, the methods loadUrl and parse are NOT called. What you are really doing is calling the "stubbed" fetchAndParse method.
I'm not really experienced in PHP, so I don't want to try and fix your test. Hopefully someone else can do that.
You misunderstood how mocking works. If you use dependency injection to set a helper object in your class, then you can mock your injected object. After that you can simulate the behavior (interface) of the original object. The better way is to mock interfaces, because you can develop without creating any class implementing the current interface.
By your example:
interface UrlLoaderInterface {
public function load($url);
}
class YourParser {
protected $urlLoader;
protected $source;
public function setUrlLoader(UrlLoaderInterface $urlLoader) {
$this->urlLoader = $urlLoader;
}
public function fetchAndParse($url) {
$this->loadUrl($url);
$this->parse();
}
public function loadUrl($url) {
$this->source = $this->urlLoader->load($url);
}
public function parse() {
}
}
Mock::generate('UrlLoaderInterface', 'MockUrlLoader');
class TestYourParser extends UnitTestCase {
public function testShouldCallUrlLoaderByFetchAndParse() {
$testUrl = 'http://url';
$urlLoader = new MockUrlLoader();
$urlLoader->expectOnce('load', array($testUrl));
$urlLoader->returns('load', 'source', array($testUrl));
$parser = new YourParser();
$parser->setUrlLoader($urlLoader);
$parser->fetchAndParse($testUrl);
}
}
Btw. your example is a fail, because method names cannot contain words like 'and', 'or', 'if', etc... A method is allowed to do only one thing. If you use these words then you can be sure that you have a bad designed code.
Related
I'm writing some unit-tests for a typo3 v9.5 extension, and I can not figure how to properly mock objects when the under-test function is instanciating them using GeneralUtility::makeInstance(ObjectManager::class)->get(...)
If possible, I'd like to use the prophecy framework, but that's not mandatory.
For example, if the function under-test is like:
public function getRootline($pageUid) {
$pageService = GeneralUtility::makeInstance(ObjectManager::class)->get(PageService::class);
return $pageService->getRootLine($pageUid);
}
How could I test this and mock the Pageservice class?
I tried different variations of this solution:
$pageServiceProphecy = $this->prophesize(PageService::class);
$pageServiceProphecy->getRootLine($pageUid)->shouldBeCalled()->willReturn($myRootLine);
$omProphecy = $this->prophesize(ObjectManager::class);
$omProphecy->get(PageService::class)->shouldBeCalled()->willReturn($pageServiceProphecy);
GeneralUtility::setSingletonInstance(ObjectManager::class, $omProphecy->reveal());
But I get various cryptic errors every time.
The given version raises the error:
TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException: A cache with identifier "cache_runtime" does not exist.
Actually you should make use of dependency injection in the class which exposes the getRootline() API, then you can have the PageService injected this way:
final class MyCustomClass {
private PageService $pageService;
public function __construct(PageService $pageService)
{
$this->pageService = $pageService;
}
public function getRootline(int $pageUid)
{
return $this->pageService->getRootLine($pageUid);
}
}
While testing you can inject a mock instead:
$pageService = $this->prophesize(PageService::class);
$myCustomObject = new MyCustomClass($pageService->reveal());
Suppose I have this class:
class SomeClass
{
// Top level function
public function execute($command)
{
// Get output from system tool
$output = $this->runTool($command);
// Check output for errors
if ($this->hasError($output))
return false;
// And parse success response from tool
return $this->parseOutput($output);
}
// There we're make a call to system
private function runTool($command)
{
return `/some/system/tool $command`;
}
[...]
}
I do not want to run system tool in my test, I want to replace a system call with predefined output.
So, the question is - should I create another class, move system call in it and mock that class in the test, or I can mock only that function of class which I will test?
Sure, both approaches will work, but which of them will be serve testing purposes better?
If you follow the single responsibility principle, you won't have this problem. Your class does not need to know how system calls are made, so you will have to use another class. You mock that.
IMO, in most cases when you need to mock protected or private methods, they do stuff that should be into another class and be mocked.
I would say it really depends on your infrastructure. Sometimes it is better to use Mock, sometimes Stub.
If the case is, that the class you want to test contains this unwanted method - use Mock and mock only this one function. That will make you sure, that any changes made to that class will be handled by the test.
If the unwanted function is a part of i.e. injected service or another class, which is not the domain of this particular test, you can create a stub.
You can't test private method, you can use a workaround and invoke it via reflection as described in this article and discussed in this SO QUESTION
But i suggest you to change the method visibility to protected and mock only the behaviour of the runTool method.
As example, suppose the following modified version of your class (i don't know how other method work so i suppose that you want to test their behaviour and take this implementation as example):
<?php
namespace Acme\DemoBundle\Service;
class SomeClass
{
// Top level function
public function execute($command)
{
// Get output from system tool
$output = $this->runTool($command);
// Check output for errors
if ($this->hasError($output))
return false;
// And parse success response from tool
return $this->parseOutput($output);
}
// There we're make a call to system
protected function runTool($command)
{
return `/some/system/tool $command`;
}
private function hasError($output)
{
return $output == "error";
}
private function parseOutput($output)
{
return json_decode($output);
}
}
As suppose the following test case:
<?php
namespace Acme\DemoBundle\Tests;
class SomeClassTest extends \PHPUnit_Framework_TestCase {
public function testCommandReturnError()
{
$mock = $this->getMockBuilder('Acme\DemoBundle\Service\SomeClass')
->setMethods(array('runTool'))
->disableOriginalConstructor()
->getMock()
;
$mock
->expects($this->exactly(1))
->method('runTool')
->with("commandName")
->will($this->returnValue("error"));
$this->assertFalse($mock->execute("commandName"));
}
public function testCommandReturnCorrectValue()
{
$mock = $this->getMockBuilder('Acme\DemoBundle\Service\SomeClass')
->setMethods(array('runTool'))
->disableOriginalConstructor()
->getMock()
;
$mock
->expects($this->exactly(1))
->method('runTool')
->with("commandName")
->will($this->returnValue('{"title":"myTitle"}'));
$returnValue = $mock->execute("commandName");
$this->assertEquals("myTitle", $returnValue->title);
}
}
Hope this help
Using PHPUnit and a mock object, I am trying to test some code that uses get_class to determine if an object is included by a filter or not.
Here is the class to be tested:
class BlockFilter implements FilterInterface
{
private $classes;
public function __construct(array $classes = array())
{
$this->classes = $classes;
}
public function isIncluded(NodeTraversableInterface $node)
{
if (Type::BLOCK != $node->getDocumentType()) {
return false;
}
if (! empty($this->classes)) {
/*** HERE IS THE PROBLEM: ***/
return in_array(get_class($node), $this->classes);
}
return true;
}
}
Here is the method from my unit test:
public function testIfContainerBlockIsIncluded()
{
$containerBlock = $this->getMock('Pwn\ContentBundle\Document\ContainerBlock');
$containerBlock->expects($this->any())->method('getDocumentType')->will($this->returnValue(Type::BLOCK));
$filter = new BlockFilter(array('Pwn\ContentBundle\Document\ContainerBlock'));
$this->assertTrue($filter->isIncluded($containerBlock));
}
The mock object $containerBlock behaves like the real object Pwn\ContentBundle\Document\ContainerBlock; even code using instanceof works (because PHPUnit makes it a subclass of the real class, I believe).
The code being tested uses get_class to get a string value of the class and compare it with an array of expected class names. Unfortunately, for the mock object, get_class returns something like this:
Mock_ContainerBlock_ac231064
(the _ac231064 suffix changes on each invocation).
This causes my test to fail, so what are my options?
Rework the code to avoid using get_class? This implies get_class should not be used when trying to write testable code.
Use a real instance of the ContainerBlock class instead of a mock? This means we are effectively testing both classes at once.
Some other awesomely clever trick that you're all going to suggest??? ;)
Thanks for any help...
Pass the Mock's class name in the test:
new BlockFilter(array(get_class($this->containerBlock)));
I've got an Object Oriented library I wanted to add a method to, and while I'm fairly certain I could just go into the source of that library and add it, I imagine this is what's generally known as A Bad Idea.
How would I go about adding my own method to a PHP object correctly?
UPDATE ** editing **
The library I'm trying to add a method to is simpleHTML, nothing fancy, just a method to improve readability. So I tried adding to my code:
class simpleHTMLDOM extends simple_html_dom {
public function remove_node() {
$this->outertext = "";
}
}
which got me: Fatal error: Call to undefined method simple_html_dom_node::remove_node(). So obviously, when you grab an element in simpleHTML it returns an object of type simple_html_dom_node.
If I add the method to simple_html_dom_node my subclass isn't what will be created by simplHTML ... so stuck as to where to go next.
A solution would be to create a new class, that extends the one from your library -- and, then, use your class, which have have all methods of the original one, plus yours.
Here's a (very quick and simple) example :
class YourClass extends TheLibraryClass {
public function yourNewMethod() {
// do what you want here
}
}
And, then, you use your class :
$obj = new YourClass();
$obj->yourNewMethod();
And you can call the methods of the TheLibraryClass class, as yours inherits the properties and methods of that one :
$obj->aMethodFromTheLibrary();
About that, you can take a look at the Object Inheritance section of the manual.
And, as you guessed, modifying a library is definitly a bad idea : you'll have to re-do that modification each time you update the library !
(One day or another, you'll forget -- or one of your colleagues will forget ^^ )
You could do it with inheritance, but you could also use a decorator pattern if you do not need access to any protected members from SimpleHtml. This is a somewhat more flexible approach. See the linked page for details.
class MySimpleHtmlExtension
{
protected $_dom;
public function __construct(simple_html_dom $simpleHtml)
{
$this->_dom = $simpleHtml;
}
public function removeNode(simple_html_dom_node $node)
{
$node->outertext = '';
return $this;
}
public function __call($method, $args)
{
if(method_exists($this->_dom, $method)) {
return call_user_func_array(array($this->_dom , $method), $args));
}
throw new BadMethodCallException("$method does not exist");
}
}
You'd use the above like this
$ext = new MySimpleHtmlExtension( new simple_html_dom );
$ext->load('<html><body>Hello <span>World</span>!</body></html>');
$ext->removeNode( $ext->find('span', 0) );
I don't why adding the method would be bad, however if you want to so without editing the library, your best bet would be to extend the class like so:
class NewClass extends OldClass {
function newMethod() {
//do stuff
}
}
class myExtenstionClass extends SomeClassInLibrary
{
public function myMethod()
{
// your function definition
}
}
As Pascal suggests... read the manual :-)
I've searched but can't quite find what I'm looking for and the manual isn't much help in this respect. I'm fairly new to unit testing, so not sure if I'm on the right track at all. Anyway, onto the question. I have a class:
<?php
class testClass {
public function doSomething($array_of_stuff) {
return AnotherClass::returnRandomElement($array_of_stuff);
}
}
?>
Now, clearly I want the AnotherClass::returnRandomElement($array_of_stuff); to return the same thing every time. My question is, in my unit test, how do I mockup this object?
I've tried adding the AnotherClass to the top of the test file, but when I want to test AnotherClass I get the "Cannot redeclare class" error.
I think I understand factory classes, but I'm not sure how I would apply that in this instance. Would I need to write an entirely seperate AnotherClass class which contained test data and then use the Factory class to load that instead of the real AnotherClass? Or is using the Factory pattern just a red herring.
I tried this:
$RedirectUtils_stub = $this->getMockForAbstractClass('RedirectUtils');
$o1 = new stdClass();
$o1->id = 2;
$o1->test_id = 2;
$o1->weight = 60;
$o1->data = "http://www.google.com/?ffdfd=fdfdfdfd?route=1";
$RedirectUtils_stub->expects($this->any())
->method('chooseRandomRoot')
->will($this->returnValue($o1));
$RedirectUtils_stub->expects($this->any())
->method('decodeQueryString')
->will($this->returnValue(array()));
in the setUp() function, but these stubs are ignored and I can't work out whether it's something I'm doing wrong, or the way I'm accessing the AnotherClass methods.
Help! This is driving me nuts.
With Unit Tests you want to create 'test' classes that contain static data, and then pass those into your tested class. This removes variables from the testing.
class Factory{
function build()
{
$reader = new reader();
$test = new test($reader);
// ..... do stuff
}
}
class Factory{
function build()
{
$reader = new reader_mock();
$test = new test($reader);
// ..... do stuff
}
}
class reader_mock
{
function doStuff()
{
return true;
}
}
Because you are using Static Classes, you would have to remove AnotherClass from the program, and then recreate it so that it only contains functions that return test data. Normally though, you, don't want to actually remove classes from the program, which is why you pass classes in like the above example.