Decouple or mock? - php

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

Related

How is it possible to test services with Laravel using PhpUnit?

I would like to test some of my services, but I can not find any example on Laravel's website:
https://laravel.com/docs/5.1/testing
They show how to test simple classes, entities, controllers, but I have no idea how to test services. How is it possible to instantiate a service with complex dependencies?
Example service:
<?php
namespace App\Services;
// Dependencies
use App\Services\FooService;
use App\Services\BarService;
class DemoService {
private $foo_srv;
private $bar_srv;
function __construct(
FooService $foo_srv,
BarService $bar_srv
) {
$this->foo_srv = $foo_srv;
$this->bar_srv = $bar_srv;
}
// I would like to test these two functions
public function demoFunctionOne() {
// ...
}
public function demoFunctionTwo() {
// ...
}
}
The quickest thought that might come to the mind is to create a copy of those Service classes, but that could grow so big. This is why there's MockObject in PhpUnit.
To achieve this mock and use it as a replacement to the service class you may need to resolve it in Laravel service container.
Here is how it will look:
class DemoServiceTest extends TestCase
{
// Dependencies
use App\Services\FooService;
use App\Services\BarService;
use App\Services\DemoService;
public function testDemoFunctionOne()
{
$foo_srv = $this->getMockBuilder(FooService::class)
//->setMethods(['...']) // array of methods to set initially or they return null
->disableOriginalConstructor() //disable __construct
->getMock();
/**
* Set methods and value to return
**/
// $foo_srv->expects($this->any())
// ->method('myMethod') //method needed by DemoService?
// ->will($this->returnValue('some value')); // return value expected
$bar_srv = $this->getMockBuilder(BarService::class)
// ->setMethods(['...']) // array of methods to set initially or they return null
->disableOriginalConstructor()
->getMock();
/**
* Set methods and value to return
**/
// $bar_srv->expects($this->any())
// ->method('myMethod') //method needed by DemoService?
// ->will($this->returnValue('some value')); // return value expected
$demo_service = new DemoService($foo_srv, $bar_srv);
$result = $demo_service->demoFunctionOne(); //run demo function
$this->assertNotEmpty($result); //an assertion
}
}
We are creating new mock for both FooService and BarService classes, and then passing it when instantiating DemoService.
As you can see without uncommenting the commented chunk of code, then we set a return value, otherwise when setMethods is not used, all methods are default to return null.
Let's say you want to resolve these classes in Laravel Service Container for example then you can after creating the mocks, call:
$this->app->instance(FooService::class, $foo_srv);
$this->app->instance(BarService::class, $bar_srv);
Laravel resolves both classes, feeding the mock classes to any caller of the classes So that you just call your class this way:
$demoService = $this->app->make(DemoService::class);
There are lots of things to watch out for when mocking classes for tests, see sources: https://matthiasnoback.nl/2014/07/test-doubles/, https://phpunit.de/manual/6.5/en/test-doubles.html
I have an example using the service you gave below. The basic idea is that for dependencies you just assume that they'll work as expected and create mocks for them.
Mocks are special classes that pretend to be a different class, but don't really do anything unless you tell them to. For example, say we have a class called UserCreationService that takes a few arguments required to create a new user. This class depends on some things like a Mailer class to send a registration mail, and a UserRepository class to save the user to the database. It also does a lot of validation of the user, and we'd like to check lots and lots of edge cases for all the different possible arguments to create a user.
In this example do we really want to check that the user was saved to the database? Do we want to check for sure that the mail was really sent? We could do that, but it would take a very long time to run all our test cases. Instead we just assume that the classes our UserCreationService depends on will just do their job and we create mock classes for the dependencies. We would create mocks for the Mailer and UserRepository and just tell the test that we expect some methods to be called (like sendRegistrationMail for the Mailer) and concentrate on the logic contained in our class.
// This is the class we want to test
class UserCreationService {
// The dependencies
private $userRepository;
private $mailer;
public function __construct($userRepository, $mailer)
{
$this->userRepository = $userRepository;
$this->mailer = $mailer;
}
public function create($name, $email, $location, $age)
{
$user = new User();
// do some complex validation here. This is what we want to test
$this->validateName($name);
$this->validateEmail($email);
$this->validateLocation($location);
$this->validateAge($age);
// then call our external services
$this->userRepository->save($user);
$this->mailer->sendRegistrationMail($user);
}
}
// This is a sample test for the above class using mocking
class UserCreationServiceTest extends TestCase
{
public function testValidUserWillBeSaved() {
// The framework allows using argument tokens.
// This just means an instance of *any* user class is expected
$anyUserToken = Argument::type(User::class);
// The prophesize method creates our mock for us
// We then define its behavior
$mailer = $this->prophesize(Mailer::class);
// Let our test know we expect this method to be called
// And that we expect an instance of a user class to be passed to it
$mailer->sendRegistrationMail($anyUserToken)->shouldBeCalled();
$userRepo = $this->prophesize(UserRepository::class);
$userRepo->save($anyUserToken)->shouldBeCalled();
// Create our service with the mocked dependencies
$service = new UserCreationService(
$userRepo->reveal(), $mailer->reveal()
);
// Try calling our method as part of the test
$result = $service->create('Tom', 'tom#test.com', 'Ireland', 24);
// Do some check to see that the result we got is what we expected
$this->assertEquals('Tom', $result->getName());
}
}
This is something similar, but specific to the example you gave above:
use PHPUnit\Framework\TestCase;
class DemoServiceTest extends TestCase
{
public function testDemoFunctionOne()
{
// These are the variables that will be passed around the service
$sampleId = 1;
$myModel = new MyModelClass();
// Set up a mock FooService instance
$fooMock = $this->prophesize(FooService::class);
// Tell the test that we expect "findFoo" to be called and what to return
$fooMock->findFoo($sampleId)->willReturn($myModel);
// Set up a mock BarService instance
$barMock = $this->prophesize(BarService::class);
// Tell the test we expect "save" to be called and what argument to expect
$barMock->save($myModel)->shouldBeCalled();
// Create an instance of the service you want to test with the mocks
$demoService = new DemoService($fooMock->reveal(), $barMock->reveal());
// Call your method, get a result
$result = $demoService->demoFunctionOne($sampleId);
// Check that the result is what you want
$this->assertEquals($myModel, $result);
}
}
I'd have a look at Laravel specific stuff here and prophecy here
i am not sure about the context you have there, but I will try to have an answer for you based on an example.
Imagine that you want to test a payment gateway from a payment provider.
My approach is to make 2 payment gateways extend something like this interface:
<?php
namespace App\Payment;
interface PaymentGateway
{
public function charge($amount, $token);
public function getTestToken();
....
}
then i will create the real payment gateway that is used in the controllers or where ever you need it and for the tests the 'fake' payment and this is an exact copy of the real one but with dummy data. This you can use on the tests because it is faster and it is a 1to1 copy of the real. I think if the service it self works or not via the internet it is outside of the testing scope, at least for now. So you will end up with something like this:
<?php
namespace App\Payment;
class FakePaymentGateway implements PaymentGateway
{
private $tokens;
const TEST_CARD_NUMBER = '1234123412341234';
public function __construct()
{
$this->tokens = collect();
}
public function getTestToken()
{
return 'fake-tok_'.str_random(15);
}
....
}
and the real one:
<?php
namespace App\Payment;
class PaypalPaymentGateway implements PaymentGateway
{
...
public function __construct(PayPal $PaypalClient)
{
...
}
public function charge($amount, $token)
{
...
}
....
}
so i think in your case, when you have complex dependencies, you have to fake all of that, depending on the case, into the fake service.
the tests will look like this for the fake service:
<?php namespace Tests\Unit\Payment;
use App\Payment\FakePaymentGateway;
use Tests\TestCase;
class FakePaymentGatewayTest extends TestCase
{
use PaymentGatewayContractTests;
protected function getPaymentGateway()
{
return new FakePaymentGateway;
}
...
}
for the real one like this:
<?php namespace Tests\Unit\Payment;
use App\Payment\PaypalPaymentGateway;
use Tests\TestCase;
/**
* #group integration
*
* ./vendor/phpunit/phpunit/phpunit --exclude-group integration
*/
class PaypalPaymentGatewayTest extends TestCase
{
use PaymentGatewayContractTests;
protected function getPaymentGateway()
{
return new PaypalPaymentGateway(....);
}
...
}
for the real one you should ignore it in the phpunit when running to make the tests faster and not depending of the internet connection and so on. It is also nice to have in the tests suite, when changes from the service occur.
You will end up having a better understanding of the dependencies and maybe you will also do some refactoring. Anyway i guess it is a lot to work but when the real implemanation will change you can see that also from the tests and change it faster.
I hope my answer will help you in your service tests :).
Maybe you can use Mockery to mock the Dependencies.
We are doing this for our cases and it does the work, yet. :)
Especially the partial Mock is doing fine here.
https://laravel.com/docs/5.8/mocking#mocking-objects

PHPUnit: Mocking __get() results in "__get() must take exactly 1 argument ..."

I've got a problem with mocking an overloaded __get($index) method.
The code for the class to be mocked and the system under test that consumes it is as follows:
<?php
class ToBeMocked
{
protected $vars = array();
public function __get($index)
{
if (isset($this->vars[$index])) {
return $this->vars[$index];
} else {
return NULL;
}
}
}
class SUTclass
{
protected $mocky;
public function __construct(ToBeMocked $mocky)
{
$this->mocky = $mocky;
}
public function getSnack()
{
return $this->mocky->snack;
}
}
Test looks as follows:
<?php
class GetSnackTest extends PHPUnit_Framework_TestCase
{
protected $stub;
protected $sut;
public function setUp()
{
$mock = $this->getMockBuilder('ToBeMocked')
->setMethods(array('__get')
->getMock();
$sut = new SUTclass($mock);
}
/**
* #test
*/
public function shouldReturnSnickers()
{
$this->mock->expects($this->once())
->method('__get')
->will($this->returnValue('snickers');
$this->assertEquals('snickers', $this->sut->getSnack());
}
}
Real code is a little bit more complex, though not much, having "getSnacks()" in its parent class. But this example should suffice.
Problem is I get the following error, when executing the test with PHPUnit:
Fatal error: Method Mock_ToBeMocked_12345672f::__get() must take exactly 1 argument in /usr/share/php/PHPUnit/Framework/MockObject/Generator.php(231)
When I debug I can't even reach the test method. It seems it breaks at setting up the mock object.
Any ideas?
__get() takes an argument, so you need to provide the mock with one:
/**
* #test
*/
public function shouldReturnSnickers()
{
$this->mock->expects($this->once())
->method('__get')
->with($this->equalTo('snack'))
->will($this->returnValue('snickers'));
$this->assertEquals('snickers', $this->sut->getSnack());
}
The with() method sets the argument for the mocked method in PHPUnit. You can find more details in the section on Test Doubles.
It's a bit hidden in the comments, but #dfmuir's answer put me on the right track. Mocking a __get method is straight forward if you use a callback.
$mock
->method('__get')
->willReturnCallback(function ($propertyName) {
switch($propertyName) {
case 'id':
return 123123123123;
case 'name':
return 'Bob';
case 'email':
return 'bob#bob.com';
}
}
);
$this->assertEquals('bob#bob.com', $mock->email);
Look in the mocked magic method __get. Probably you call there one more __get method from another and not properly mocked object.
What you are doing in the setUp method of your GetSnackTest class is incorrect.
If you want the code of the __get method to be executed (which would be the point of your test> I suppose), you have to change the way you call setMethods in the setup method.
Here's the complete explanation, but here's the relevant part:
Passing an array containing method names
The methods you have identified:
Are all stubs,
All return null by default,
Are easily overridable
So, you need to call setMethods by passing null, or by passing an array that contains some methods (the ones that you really want to stub), but not- __get (because you actually want the code of that method to be executed).
The, in the shouldReturnSnickers method, you will simply want to want to call $this->assertEquals('snickers', $this->sut->getSnack());, without the preceding lines with the expect part.
This will ensure the code of your __get method is actually executed and tested.
withAnyParameters() method can help you, this works correct:
$this->mock -> expects($this -> once())
-> method('__get') -> withAnyParameters()
-> will($this -> returnValue('snikers'));

Testing code that uses get_class with PHPUnit mock objects

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)));

Creating mock Objects in PHP unit

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.

SimpleTest Mocking Problem

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.

Categories