In phpspec can i mock the return value of a method?
for example:
class MyClass()
{
public function getStaffMemberNames()
{
// db call to get array of staff member names
}
public function sortStaffMemberNames()
{
return sort($this->getStaffMemberNames());
}
}
I am interested in testing the sortStaffMemberNames method. But it relies on another class method which uses a db connection. I want to mock the getStaffMemberNames so i can easily test.
How can this be achieved?
There's no partial mocks in phpspec (you cannot mock the class under test). This is a bad practice.
You should mock your collaborators instead (database connection for example).
Related
I've been reading about testing without mocks and in general I like it. However, I've been struggling what to do when there's some third-party class included. For example if we have this class:
<?php
use External\ThirdPartyService;
use External\ThirdPartyException;
class AdapterForExternalService implements OurInterface
{
private ThirdPartyService $external;
public function __construct(ThirdPartyService $external)
{
$this->external = $external;
}
public function something(): int
{
try {
return $this->external->someMethod();
} catch (ThirdPartyException $e) {
return 1;
}
}
}
I know how to test it by mocking the external class, but is it possible to do it without mocking too?
If mocking is non-avoidable here, what if the ThirdPartyService class is final?
The something() method is not changing observable state. It only wraps the call to the third party service and turns the exception into an integer return value. The thing that is observable and that you could (in theory) assert on, is the call to the service and the returned results. This means, if you are not mocking, you will have to call that service.
To test this at all, you have to mock the dependency. If that class is really final, you can create a non-final facade for it and mock that.
It may seem silly, hope not, but I want to create a service that will return mock objects for people that uses my project so they can mock all the classes from my project and test their code.
My idea was to offer this kind of service so it can be called inside other project's test cases and obtain the appropriate mock for each test.
Is that possible? Or there are other ways to do that. Btw, I can't use any mocking library because of project's limitations.
Yes, it is possible. Under the hood the getMock method uses the PHPUnit_Framework_MockObject_Generator class. So you can use it directly:
PHPUnit_Framework_MockObject_Generator::getMock($originalClassName, $methods)
But you will lose all the expectation shortcuts like $this->once(). You will have to instantiate the expectations on your own:
$mock->expects(\PHPUnit_Framework_TestCase::once())
Look at the PHPUnit source code to see how the mocks are build
If you build the mock outside the TestCase, it will not be accounted as a mock and it's expectations won't be checked automatically.
If your mock builder service is supposed to be used exclusively from PHPUnit tests (even if those tests are not related) you can have the testcase instance passed to the mock builder, so you can build the mock the usual way:
class MockBuilderService
{
private $test;
public function __construct(PHPUnit_Framework_TestCase $test)
{
$this->test = $test;
}
public function buildVeryComplexMock()
{
$mock = $this->test->getMock('MyClass');
$mock->expects($this->test->once())
->method('foo')
->willReturn(1);
return $mock;
}
}
So you can use it from your test:
class ATest extends PHPUnit_Framework_TestCase
{
public function testFoo()
{
$mock_builder = new MockBuilderService($this);
$complex_mock = $mock_builder->buildVeryComplexMock($mock_configuration);
// the mock expectations will be checked as usual
}
}
It may seem silly, hope not, but I want to create a service that will return mock objects for people that uses my project so they can mock all the classes from my project and test their code.
My idea was to offer this kind of service so it can be called inside other project's test cases and obtain the appropriate mock for each test.
Is that possible? Or there are other ways to do that. Btw, I can't use any mocking library because of project's limitations.
Yes, it is possible. Under the hood the getMock method uses the PHPUnit_Framework_MockObject_Generator class. So you can use it directly:
PHPUnit_Framework_MockObject_Generator::getMock($originalClassName, $methods)
But you will lose all the expectation shortcuts like $this->once(). You will have to instantiate the expectations on your own:
$mock->expects(\PHPUnit_Framework_TestCase::once())
Look at the PHPUnit source code to see how the mocks are build
If you build the mock outside the TestCase, it will not be accounted as a mock and it's expectations won't be checked automatically.
If your mock builder service is supposed to be used exclusively from PHPUnit tests (even if those tests are not related) you can have the testcase instance passed to the mock builder, so you can build the mock the usual way:
class MockBuilderService
{
private $test;
public function __construct(PHPUnit_Framework_TestCase $test)
{
$this->test = $test;
}
public function buildVeryComplexMock()
{
$mock = $this->test->getMock('MyClass');
$mock->expects($this->test->once())
->method('foo')
->willReturn(1);
return $mock;
}
}
So you can use it from your test:
class ATest extends PHPUnit_Framework_TestCase
{
public function testFoo()
{
$mock_builder = new MockBuilderService($this);
$complex_mock = $mock_builder->buildVeryComplexMock($mock_configuration);
// the mock expectations will be checked as usual
}
}
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.
The code to be tested
abstract class Parent
{
public function getSomething(){} //this has to be mocked
}
class Child extends Parent
{
public function methodWhichIsTested()
{
$something = $this->getSomething(); //call to parent method
}
}
The test
public function setUp()
{
$this->child = new Child;
}
public function testTheChildMethod()
{
$this->child->methodWhichIsTested();
}
How can I add mock expectations to the instantiated class Child?
I would like to do something like:
MockFramework->takeExistingClass('Child')->shouldRecieve('getSomething')->andReturn('whatever');
My problem is, that in the real case (not the example), the getSomething method returns a dependency, which I need to mock!
I am using Mockery but if you know how to do this with phpUnit mocks, go ahead! Maybe I'm making a basic thinking mistake, please give me a hand! Thanks.
After you clarfied in chat that the returned value of getSomething holds a dependency that is
a protected property of the abstract class, and it is injected into that abstract via another public method of the abstract
the solution is inject a mock of that dependency via that other method.
In general, you should never have the need to mock or stub behavior of the TestSubject. It is only when you are making lookups to the Global Scope or mix/hard code object creation into the TestSubject, that you might see the need for that, but these would be code smells and should be refactored instead. See Sebastian Bergmann's articles on untestable code:
Testing private methods
Testing code that uses singletons
Stubbing static methods
Stubbing hard-coded dependencies