In a silex application I have a KafkaAPiClient class which definitely has the public method postMessages.
<?php
namespace Kopernikus\KafkaWriter;
use Kopernikus\KafkaWriter\Model\AbstractMessage;
/**
* KafkaApiClient.
**/
class KafkaApiClient
{
/**
* #param AbstractMessage[] $msg
*/
public function postMessages(array $messages)
{
foreach ($messages as $message) {
$this->postMessage($message);
}
}
public function postMessage(AbstractMessage $msg)
{
...
}
}
I can call KafkaAPiClient::postMessages just fine, yet when mocking the class in a test:
<?php
namespace unit\Request;
use Kopernikus\KafkaWriter\KafkaApiClient;
/**
* MockeryMethodsNotBeingCallableTest
**/
class MockeryMethodsNotBeingCallableTest extends \PHPUnit_Framework_TestCase
{
public function testMockMethodIsCallable()
{
$leMock = \Mockery::mock(KafkaApiClient::class);
$leMock->postMessages([]);
}
}
I am getting:
1) unit\Request\MockeryMethodsNotBeingCallableTest::testMockMethodIsCallable
BadMethodCallException: Method Mockery_11_Kopernikus_KafkaWriter_KafkaApiClient::postMessages() does not exist on this mock object
~/le-project/tests/unit/Request/MockeryMethodsNotBeingCallableTest.php:14
I am confused, I was expecting for the mock to not do anything yet allow the methods to be called so that I later could add my expectations on it.
Though I have found a solution, I am still wondering if it is possible to mock all the methods by default, and later check if certain ones have been called.
There exists shouldIgnoreMissing method on the mock object. Calling that does exactly what it says on the tin, that is: ignoring calls to not yet defined methods, resulting in a mock that does nothing:
$leMock = \Mockery::mock(KafkaApiClient::class);
$leMock->shouldIgnoreMissing()
$leMock->postMessages([]);
And by nothing, it means nothing. I got into an other error for my queue when I instantiated the mock that way, as methods will return null by default and their return value has to be explicitly stated.
$msg = new Message('dummy-message');
$this->kafkaQueue
->shouldIgnoreMissing()
->shouldReceive('getMessages')->andReturn([$msg]);
Any call to getMessages will now return exactly the array [$msg].
Alternatively, one can be very explicit about what methods are called with Mockery, by adding shouldReceive:
public function testMockMethodIsCallable()
{
$leMock = \Mockery::mock(KafkaApiClient::class);
$leMock->shouldReceive('postMessages');
$leMock->postMessages([]);
}
Related
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
It started when I was performing null checks everywhere to make sure I have the necessary entities for my interactor. Fortunately, I came across this post which points towards not allowing the entities to be in an invalid state do the check in your constructor. Now my Interactors use protected static $request to state explicitly which entities they require, which are then passed in during instantiation. I chose static so the check could be done prior to creating an instance of the Interactor.
abstract class Interactor {
protected static $request = [];
protected $entities = [];
final public function __construct(Entity ...$entities) {
$this->setEntities(...$entities);
$this->checkEntities();
}
final private function setEntities(Entity ...$entities) {
foreach($entities as $entity) {
$this->setEntity($entity);
}
}
final private function setEntity(Entity $entity){
$className = get_class($entity);
if (!in_array($className, static::$request)){
throw new Exception("Not a requested entity");
}
$this->entities[$className] = $entity;
}
final private function checkEntities(){
if (count(static::$request) != count($this->entities))
throw new Exception("Entity mismatch");
foreach(static::$request as $index=>$name) {
if (!array_key_exists($name, $this->entities))
throw new Exception("Missing requested entity ($name)");
if (!is_a($this->entities[$name], $name))
throw new Exception("Not the specified entity");
}
}
final public static function getRequest(){
return array_values(static::$request);
}
}
Ok great, now I just do the check in a single location and I don't need to worry about performing null checks at the beginning of my functions. The problem with the way I am going about it now is that my Interactor is checking the class name against a static class name request array. Thus, when I DI the mocked entities during testing, my parent Interactor throws an exception saying it isn't in the pre approved list.
To demonstrate is the following simplified Chess example:
class Chess extends Interactor {
protected static $request = ['Piece','Engine','Board'];
}
Then we have our Entities:
abstract class Entity {}
class Piece extends Entity {}
class Engine extends Entity {}
class Board extends Entity {}
And finally our test:
class ChessTest extends TestCase {
function setUp(){
$this->piece = $this->getMockBuilder(Piece::class)->getMock();
$this->engine = $this->getMockBuilder(Engine::class)->getMock();
$this->board = $this->getMockBuilder(Board::class)->getMock();
$this->chess = new Chess($this->piece, $this->engine, $this->board);
}
function testCanSetup(){
$this->assertTrue(
is_a($this->chess, Chess::class)
);
}
}
Which throws Exception: Interactor receiving entity not requested (Mock_Piece_faaf8b14)
Of course Mock_Piece_faaf8b14 is not going to be in our static::$request array, so this is destined to throw an exception.
The workaround I have come up with so far is to include in Entity:
public function getClassName(){
return get_called_class();
}
Then in Interactor->setEntity($entity) instead of using get_class($entity) I would use $entity->getClassName() which then becomes trivial to mock.
I thought the way I had created the Interactor was inline with what the previously mentioned post was getting at, only take the entities in the constructor. However, it all feel apart when I injected mocked entities.
1) Is there a way to avoid getClassName() in my entities?
2) Is there something in the entities I can mock that gets called in get_class() instead?
Thank you for your help!
You are checking to see if the name of your class is one of the keys in your $request array. And it isn't. The keys in your array are numerical 0, 1, 2 so you are throwing the exception. I think that you want to use in_array instead.
Though at the same time, this still wouldn't pass with the mock because you are checking to see if the class name is in $request. So the name won't be there at all either and the exception will still be thrown.
If all that your Interactor class is doing is making sure that the correct objects are passed into the constructor why not just use PHP's native type hinting?
Your Chess class becomes:
class Chess {
public function __construct(Piece $piece, Engine $engine, Board $board) { }
}
PHP will make sure that the passed in objects are of the correct type and will allow you to mock them for testing.
You get the type checking that you are looking for without need to use getClassName() at all.
I am trying to test the below class using PHPUnit
class stripe extends paymentValidator {
public $apiKey;
public function __construct ($apiKey){
$this->apiKey = $apiKey;
}
public function charge($token) {
try {
return $this->requestStripe($token);
} catch(\Stripe\Error\Card $e) {
echo $e->getMessage();
return false;
}
}
public function requestStripe($token) {
// do something
}
}
My test scripts is like the below:
class paymentvalidatorTest extends PHPUnit_Framework_TestCase
{
/**
* #test
*/
public function test_stripe() {
// Create a stub for the SomeClass class.
$stripe = $this->getMockBuilder(stripe::class)
->disableOriginalConstructor()
->setMethods(['requestStripe', 'charge'])
->getMock();
$stripe->expects($this->any())
->method('requestStripe')
->will($this->returnValue('Miaw'));
$sound = $stripe->charge('token');
$this->assertEquals('Miaw', $sound);
}
}
With my test script I was expecting the test double of stripe::charge() method will do exactly as the defined in the original class and the stripe::requestStripe() will return 'Miaw'. Therefore, $stripe->charge('token') should also return 'Miaw'. However, when I run the test I get:
Failed asserting that null matches expected 'Miaw'.
How should I fix this ?
Where you're calling setMethods, you're telling PHPUnit that the mock class should mock the behaviour of those methods:
->setMethods(['requestStripe', 'charge'])
In your case it looks like you want to partially mock the class, so that requestStripe() returns Miaw, but you want charge to run its original code - you should just remove charge from the mocked methods:
$stripe = $this->getMockBuilder(stripe::class)
->disableOriginalConstructor()
->setMethods(['requestStripe'])
->getMock();
$stripe->expects($this->once())
->method('requestStripe')
->will($this->returnValue('Miaw'));
$sound = $stripe->charge('token');
$this->assertEquals('Miaw', $sound);
While you're at it you may as well specify how many times you expect requestStripe() to be called - it's an extra assertion with no extra effort, as using $this->any() doesn't provide you with any added benefit. I've included using $this->once() in the example.
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
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'));