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.
Related
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.
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
}
}
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).
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