I'm trying to create a pretty standard unit test where I call a method and assert it's response, however the method I'm testing calls another method inside the same class which does a little bit of heavy lifting.
I want to mock that one method but still execute the method I'm testing as is, only with the mocked value returned from the call to the other method.
I've dumbed down the example to make it as simple as possible.
class MyClass
{
// I want to test this method, but mock the handleValue method to always return a set value.
public function testMethod($arg)
{
$value = $arg->getValue();
$this->handleValue($value);
}
// This method needs to be mocked to always return a set value.
public function handleValue($value)
{
// Do a bunch of stuff...
$value += 20;
return $value;
}
}
My attempt at writing the tests.
class MyClassTest extends \PHPUnit_Framework_TestCase
{
public function testTheTestMethod()
{
// mock the object that is passed in as an arg
$arg = $this->getMockBuilder('SomeEntity')->getMock();
$arg->expects($this->any())
->method('getValue')
->will($this->returnValue(10));
// test handle document()
$myClass = new MyClass();
$result = $myClass->testMethod($arg);
// assert result is the correct
$this->assertEquals($result, 50);
}
}
I have tried mocking the MyClass object, but when I do that and call the testMethod it always returns null. I need a way to mock the one method but leave the rest of the object intact.
You can mock the class that you are testing and specify the method that you want to mock.
$mock = $this->getMockBuilder('MyClass')
->setMethods(array('handleValue'))
->getMock();
$mock->expects($this->once())
->method('handleValue')
->will($this->returnValue(23)) //Whatever value you want to return
However, IMO this is not the best idea for your tests. Testing like this will make refactoring much more difficult. You are specifying the implementation of the class rather than the behavior that the class is supposed to have. If handleValue is doing a lot of complicated work that makes testing difficult, consider moving the logic into a separate class and injecting that into your class. Then you can create a mock of that class and pass it in to testMethod. Doing so will give you the added advantage of making MyClass more extensible if handleValue needs to adapt its behavior.
http://www.oodesign.com/strategy-pattern.html
As a general rule, you should not mock the system that you are testing.
You can specify which methods to mock (partial mock) with setMethods():
// Let's do a `partial mock` of the object. By passing in an array of methods to `setMethods`
// we are telling PHPUnit to only mock the methods we specify, in this case `handleValue()`.
$csc = $this->getMockBuilder('Lightmaker\CloudSearchBundle\Controller\CloudSearchController')
->setConstructorArgs($constructor)
->setMethods(array('handleValue'))
->getMock();
// Tell the `handleValue` method to return 'bla'
$csc->expects($this->any())
->method('handleValue')
->with('bla');
Any other methods in the class not specified in the array you give setMethods() will be executed as is. If you do not use setMethods all methods will return NULL unless you specifically set them.
Related
I need to mock a zf2 controller and keep one real function: "getStopWords".
I tried this answer adapted below:
public function createMockExecpt()
{
// create mock to get names of all functions
$mock = $this->getMockBuilder('Controller\CollectionsController')->disableOriginalConstructor()->getMock();
$reflection = new MyReflectionClass($mock);
$functionsToMock = $reflection->getAllfunctionNamesExcept(["getStopWords"]);
// create mock but don't mock one function
return $this->getMock('Controller\CollectionsController', $functionsToMock);
}
but got an error about redefining a class.
// Cannot redeclare Mock_CollectionsController_d61a5651::__clone()
I think this happens because I need an instance of the controller to find out all the functions it has. However I can't make an instance of the controller in this context, which is why I need a mock. But I can't make more than one mock of a class within the same test, so I'm stuck.
My problem was that I thought you need an instance of the class to get all the methods.
Turns out all you need is the class name!
public function testGetStopWords()
{
// get the class methods the controller has, except getStopWords
$methodsToMock = array_diff(
get_class_methods("Controller\CollectionsController"),
["getStopWords"]
);
// use setMethods to determine which methods to mock
$mockController = $this->getMockBuilder("Controller\CollectionsController")
->setMethods($methodsToMock)
->disableOriginalConstructor()
->getMock();
}
I can not set value for inner method when I try to test. Here I have written a sample class. I have created mock object for same class but does not effect.
class A
{
public function OneTest()
{
if($this->TwoTest()){
return true;
}
}
public function TwoTest()
{
// return somethings
}
}
I am new at phpunit test writing. if some one expert help me that good for me. I want to test this method. I have tried with:
class ATest extends \PHPUnit_Framework_TestCase
{
public function testOne()
{
$helper = new testOne();
// trying to set TwoTest() method value but does not effect.
$mock = $this->createMock(A::class);
$mock->method("TwoTest")
->willReturn(true);
$this->assertTrue($helper->OneTest();
}
}
Actually I do not know how to use my mocking method result. My actual implementation in twoTest method contains some db related code. I do not want to run db code in testing time.
You are pretty close with your mock. What you want to do is called partial mocking. This is done by creating a mock of A with only TwoTest being mocked, i.e. it will now always return true and never actually call the real code inside the original implementation in A, whereas all other methods still act as before. Therefore calling $mock->OneTest() should return the expected result. Since you make both calls on the (partially) mocked instance, you won't need $helper. So your test would probably look something like this:
public function testOneWhenTwoTestReturnsTrue()
{
$mock = $this->getMockBuilder(A::class)
->setMethods(["TwoTest"])
->getMock();
$mock->method("TwoTest")
->willReturn(true);
$this->assertTrue($mock->OneTest();
}
Notice that I use getMockBuilder() instead of just createMock() and setMethods() is what we need for your test. We only overwrite the one method we want to mock, the rest will behave as defined in the original class. To quote the docs:
setMethods(array $methods) can be called on the Mock Builder object to specify the methods that are to be replaced with a configurable test double. The behavior of the other methods is not changed. If you call setMethods(null), then no methods will be replaced.
I'm admittedly very rusty with unit testing.
I have the following function in my Registration.php:
protected function _showModal($request) {
if (Store::isUserSupported()) {
return false;
}
return true;
}
I started writing the following test but I know I'm missing some key items:
public function testUserSupported() {
$mockStore = $this->getMockClass('Store', array('isUserSupported'));
$mockStore::staticExpects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$mockStore::isUserSupported();
$plugin = $this->getMockBuilder('Registration')
->setMethods(array('_showModal'))
->disableOriginalConstructor()
->getMock();
$plugin = $this->getPublicClass($plugin);
$plugin->expects($this->once())
->method('_showTermsModal')
->will($this->returnValue(true));
}
The $mockStore part is getting called, but not sure how to tie it into my $plugin call.. I want to write a test to mock Store::isUserSupported() returning true within the showModal function. Any words of advice?
You should avoid static calls in your code, because they make your code coupled together and hard to change and hard to maintain. It also makes it harder to tests, as you are suffering here.
Instead of static calls, pass the collaborators needed to work. In your code example, the class you want to test would receive as parameter the Store class, instead of calling it statically.
By doing this change, you can now create a mock for the method isUserSupported() of the Store class, and pass the mock to the object under test, that will efectively use the mocked object.
$mockStore = $this->getMock('Store', array('isUserSupported'));
$mockStore->expects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$object_under_test = new Registration( $mockStore );
$object_under_test->showModal($request); // Only in case showModal is public, otherwise call public method
If the Store class is only needed for that method, and you don't want to pass it as dependency in the constructor, you can pass it on the method itself.
$mockStore = $this->getMock('Store', array('isUserSupported'));
$mockStore->expects($this->once())
->method('isUserSupported')
->will($this->returnValue(true));
$object_under_test = new Registration();
$object_under_test->showModal($mockStore, $request); // Only in case showModal is public, otherwise call public method
Also, you shouldn't test protected/private methods of your classes, since they are low level implementation details that your tests don't need to know about. You should only make calls to public methods in your tests. Otherwise, the tests become very coupled with the real implementation, and if you refactor your code, you will most likely have to change the tests too.
Given this class:
class MyBuilder {
public function build($param1, $param2) {
// build dependencies ...
return new MyClass($dep1, $dep2, $dep3);
}
}
How can I unit test this class?
Unit-testing it means I want to test its behavior, so I want to test it builds my object with the correct dependencies. However, the new instruction is hardcoded and I can't mock it.
For now, I've added the name of the class as a parameter (so I can provide the class name of a mock class), but it's ugly:
class MyBuilder {
public function build($classname, $param1, $param2) {
// build dependencies ...
return new $classname($dep1, $dep2, $dep3);
}
}
Is there a clean solution or design pattern to make my factories testable?
Factories are inherently testable, you are just trying to get too tight of control over the implementation.
You would check that you get an instance of your class via $this->assertInstanceOf(). Then with the resulting object, you would make sure that properties are set properly. For this you could use any public accessor methods or use $this->assertAttribute* methods that are available in PHPUnit.
http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.assertEquals
Many of the common assertions also have the ability to check attributes for protected and private properties.
I wouldn't specify the classname in your parameter list, as your usage is that the factory will only return one type and it is only the dependencies that are changed. Making it return a mock object type is unnecessary and makes your test more complicated.
The test would end up looking like this:
public function testBuild() {
$factory = new MyBuilder();
//I would likely put the following into a data provider
$param1 = 'foo';
$param2 = 'bar';
$depen1 = 'boo';
$depen2 = 'baz';
$depen3 = 'boz';
$object = $factory->build($param1, $param2);
$this->assertInstanceOf('MyClass', $object);
//Check the object definition
//This would change depending on your actual implementation of your class
$this->assertAttributeEquals($depen1, 'attr1', $object);
$this->assertAttributeEquals($depen2, 'attr2', $object);
$this->assertAttributeEquals($depen3, 'attr3', $object);
}
You are now making sure that your factory returns a proper object. First by making sure that it is of the proper type. Then by making sure that it was initialized properly.
You are depending upon the existence of MyClass for the test to pass but that is not a bad thing. Your factory is intended to created MyClass objects so if that class is undefined then your test should definitely fail.
Having failing tests while your developing is also not a bad thing.
So what do you want to test?
so I want to test it builds my object with the correct dependencies.
I do see a problem with this. It's either possible that you can create an object with incorrect dependencies (which should not be the case in the first place or tested in other tests, not with the factory) or you want to test a detail of the factory that you should not test at all.
Otherwise - if it's not mocking the factory what you're looking for - I see no reason why a simple
$actual = $subject->build($param1, $param2);
$this->assertInstanceOf('MyClass', $actual);
would not make it. It tests the behavior of the factory build method, that it returns the correct type.
See as well Open-Close-Principle
For tests, you can just create your MockBuilder which extends from your Builder:
class MyMockBuilder extends MyBuilder {
public function build($param1, $param2) {
// build dependencies ...
return new MyMockClass($dep1, $dep2, $dep3);
}
}
Making the classname a parameter 1:1 seems not practical to me, because it turns the factory over into something different. The creating is a detail of the factory, nothing you externalize. So it should be encapsulated. Hence the MockBuilder for tests. You switch the Factory.
As I see it, you ned to verify two things for that builder:
the correct instance is returned
values, that are injected are the right ones.
Checking instance is the easy part. Verifying values needs a bit of trickery.
The simples way to do this would be altering the autoloader. You need to make sure that when MyClass is requested for autoloader to fetch, instead of /src/app/myclass.php file it loads /test/app/myclass.php, which actually contains a "transparent" mock (where you with simple getters can verify the values).
bad idea
Update:
Also, if you do not want to mess with autoloader, you can just at th top of your myBuilderTest.php file include the mock class file, which contains definition for MyClass.
... this actually seems like a cleaner way.
namespace Foo\Bar;
use PHPUnit_Framework_TestCase;
require TEST_ROOT . '/mocks/myclass.php'
class MyBuilderTest extends PHPUnit_Framework_TestCase
{
public function MyBuilder_verify_injected_params_test()
{
$target = new MyBuilder;
$instance = $target->build('a', 'b');
$this->assertEquals('a', $instance->getFirstConstructorParam();
}
}
Is there a way to create a mock class with PHPUnit which I can then create a new instance of by using its class name?
I have an interface which defines two methods. Something like:
interface FooInterface {
function getA();
function getB();
}
I then have another class which accepts a class name, creates an instance of that class, checks if it is an instance of what it expects (FooInterface) and then calls two methods on that class to get some information.
class FooInfo {
protected $a;
protected $b;
public function __construct($fooClass) {
$foo = new $fooClass;
if (!($foo instanceof FooInterface)) {
throw new \Exception();
}
$this->a = $foo->getA();
$this->b = $foo->getB();
}
}
I know how to mock an object just fine. The problem is, since this class accepts a class name, not an object (it is a part of a Manager which creates instances of the given class as needed), I can't use a normal mock object.
I tried to make a mock object then use that class name. It seems to create the object just fine, and even seems to have the functions I mocked out. However, it doesn't seem to follow the will($this->returnValue('myValue')) portion I set up later.
public function testConstruct()
{
$foo = $this->getMockForAbstractClass('Foo', array('getA', 'getB'));
$foo->expects($this->any())->method->('getA')->will($this->returnValue('a'));
$foo->expects($this->any())->method->('getB')->will($this->returnValue('b'));
$copyClass = get_class($foo);
$copy = new $copyClass();
// Passes
$this->assertTrue(method_exists($copy, 'getA');
// Fails, $copy->getA() returns null.
$this->assertEquals($copy->getA(), $foo->getA());
}
So, it does have the functions which were mocked, but they all return null.
Any ideas?
To use the new keyword in the constructor of a class is a rather bad habit exactly for the reasons you're experiencing now, even given your flexible use case.
Your test will not work because the mock you create will never be used, since your class will always create a real instance of the injected classname.
That said, what you want to do can be done with padraic's excellent mocking library mockery!
What you need is an 'instance mock':
$mock = \Mockery::mock('overload:MyNamespace\MyClass');
You can define your expectations on that mock which will be transferred to the real object, as soon as it is instantiated.
Integrating mockery with phpUnit is easy and well explained in the readme of the project.
And btw. each unit test should optimally make one assertion only!
The methods getA and getB return null because you have not specified what they should return. You did specify that for the abstract $foo by calling some methods. There's no way around that.
Since it's hard (if not impossible) to test the function, I would rewrite the code itself. Testing would be easy if your constructor would require a class instance rather than a class name. If you also must accept strings, you could write a few lines to check for a string input and create a class if a string is provided.