I've been trying to use Mockery to assert that a method is called from within another method, when an exception is thrown. So as an example:
public function testOtherMethodIsCalled() {
$client = m::mock('Client');
$client
->shouldReceive('getFoo')
->andThrow(new FooNotAvailableException);
$controller = m::mock('Controller[otherMethod]');
$controller
->shouldReceive('otherMethod')
->once();
$controller->setClient($client);
$controller->firstMethod();
}
Obviously the names have been simplified, but that's line for line identical in every other way to what I have. In the code, when FooNotAvailableException is caught I return the call to otherMethod().
The problem is that when run it, I get this error:
Mockery\CountValidator\Exception: Method otherMethod() from Controller should be called exactly 1 times but called 0 times.
That's because internally the original, unmocked otherMethod() is being called. If I were to call it from within the test, like this:
$controller->otherMethod();
The test passes.
Why is that, and how would I write the test for what I want to test?
It's hard to tell without the full code source, but i believe that this is what is occurring:
Your code:
$client = m::mock('Client');
$client
->shouldReceive('getFoo')
->andThrow(new FooNotAvailableException);
So far so good. No problem yet.
$controller = m::mock('Controller[otherMethod]');
$controller
->shouldReceive('otherMethod')
->once();
$controller->setClient($client);
$controller->firstMethod();
Now, we run into a problem. I'm assuming that the code under test is redirecting to another URL. When that occurs, you're going to instantiate another Controller. The Controller that you instantiated is not going to be the Controller that is instantiated by "m::mock('Controller[otherMethod]')". So, obviously the mocked instance won't ever receive the "otherMethod".
Depending on how your code under test is actually written, the proper way to test this may be to assert that a Redirect::to has been called from the function that handles the FooNotAvailableException.
Related
Consider this code:
<?php namespace App\Services;
use App\Services\AnotherService;
class SomeService {
public function someMethod() {
$anotherService = \App::make(AnotherService::class);
}
}
My intention is to get a class object with all its dependencies resolved. But in this specific case, I would like that the object's __construct has been executed as well. I have tried and by code above, the $anotherService object's __construct method is not executed.
Therefore I can achieve what I need by doing -
$anotherService = \App::make(AnotherService::class);
$anotherService->__construct();
Can it be done with single line instead of redundantly each time call construct method after instantiating the object? - Because that is what __construct method is made for - executing automatically. But I have noticed that for some reason Laravel's automatic dependency resolving skips the __construct execution.
Note, that new AnotherService() is not an option for me, as well as using the __construct method of SomeService class. I would like to make an object inside the someMethod method.
Class AnotherService currently does not have any dependencies. It has just some random variable updates inside the __construct, like:
public function __construct() {
$this->varA = true;
$this->varB = 'Some Value';
}
Why do I need to resolve this class (instead of using new AnotherClass())? - Simply because I want to test this call in unit tests by mocking it. For that I use this code:
$this->mock(AnotherService::class, function ($mock) {
$mock->shouldReceive('anotherMethod')->andReturn(false);
});
And then finally I have to call the method to check the response:
$anotherService = \App::make(AnotherService::class);//<-- In this moment $varA and $varB are not set because the __construct did not execute!
$response = $anotherService->anotherMethod();
// Assert...
So, in this case, for example, if anotherMethod would use any of those variables, the tests would be incorrect because their values are not set.
I believe there is either a mistake somewhere in your code or some misunderstanding.
The problem here is not in Laravel. The key here is the usage of Mockery. When creating a simple Mock for a class, its constructor is not invoked, and it's just a mock. When using $this->mock(...), you just bind a concrete instance (mock) in the container. Thus, when calling its make, that ready mock is returned. Meaning, that __construct is neither invoked by Mockery, nor by Container.
I have the following PHP function, for which I am trying to write a PHPUnit test:
public function getDisplayMode()
{
if($this->request->query->get('master_video'))
{
return 'listTranslations';
}
else
{
return 'default';
}
}
The line $this->request->query->get('master_video') is what I am having trouble with.
I have mocked up the request object, so if it were just $this->request->get('master_video'), I could easily use the method method to tell my system what should be returned by get.
But I don't know how to do that when the query property is assumed to be present. (I don't know of any property method on my mock object, for example.)
Is there a way to define what should be returned by the get method here?
Create a mock of query and specify what happens on ->get(). The test would end up looking something like this:
public function testGetDisplayMode() {
$mockQuery = $this->getMockBuilder('Query') // Or whatever the query class is
->setMethods(['get'])
->getMock();
$mockQuery->expects($this->once())
->method('get')
->with('master_video')
->willReturn('foo');
$mockRequest = $this->getMockBuilder('Request') // Or whatever the request is
->getMock();
$mockRequest->query = $mockQuery;
$sut = new Bar($mockRequest) // Or however you instantiate the class with the mock request.
$mode = $sut->getDisplayMode();
// Do your assertions on the returned mode here.
}
As a general rule, I have found when I am doing things like this where I have a mock object returning a mock object that it is a code smell. Your method here doesn't need the $this->request it needs the query object. You should be giving this directly to the object or passing it into this method.
Without knowing more about the class that you are making, I can't give more advice.
If something is hard to write a test for, it is a sign that you have not optimally designed your code.
I'm new to Laravel and the concept of the IoC. I was following the great tutorials over a Nettuts (http://net.tutsplus.com/tutorials/php/testing-laravel-controllers/) and was able to successful test my controller. However I wanted to isolate the controller by mocking the database. As soon as I attempted to inject my mocked object into the IoC I get the following error:
Cannot modify header information - headers already sent by (output started at /Users/STRATTON/Dev/SafeHaven/vendor/phpunit/phpunit/PHPUnit/Util/Printer.php:172)
The line it's referring to outputs PHPUnit's buffer using the 'print' construct. Something is causing output to be sent before headers are set but I can't track down the problem.
I'm able to run all my tests successfully when the controller calls the real model and makes the database call. At the same time I'm able to mock the object successfully and exercise mock without error. But as soon as I attempt to inject the mocked object using App::instance() the error appears.
I've also tested this with PHPUnit's mocks and get the same results. Am I mocking the object properly? Do I have a problem with namespacing? Am I missing something that's outputting content?
Controller:
<?php namespace App\Controllers;
use App\Models\Repositories\ArticleRepositoryInterface;
class HomeController extends BaseController {
protected $articles;
public function __construct(ArticleRepositoryInterface $articles)
{
$this->articles = $articles;
}
public function index()
{
$articles = $this->articles->recent();
return \View::make('home.index')
->with('articles', $articles);
}
}
TestCase
<?php namespace Tests\Controllers;
class HomeControllerTest extends \TestCase {
public function testIndex()
{
$mocked = \Mockery::mock('App\\Models\\Repositories\\ArticleRepositoryInterface');
$mocked->shouldReceive('recent')->once()->andReturn('foo');
\App::instance('App\\Models\\Repositories\\ArticleRepositoryInterface', $mocked);
//$mocked->recent();
$this->call('GET', '/');
$this->assertResponseOk();
$this->assertViewHas('articles');
}
}
It actually a a bug on how exception is handled during running test case, this however has been fixed, just run composer update.
Answering my own question - The reason for the error is that some part of the code is causing a PHP error or an exception to be thrown.
In this case, the problem was an Exception thrown from the View. The View was expecting that the value returned by the method recent() was an Eloquent Collection (Illuminate\Database\Eloquent\Collection), or at least something that the View could iterate over.
THe HomeControllerTest::TestIndex method was mocking the object and when recent() was called it returned 'foo'. There's no way for the View to iterate over a string so it throws an exception. The two solutions are below, the later allowing the ability to test that the view received the correct object type.
$mocked->shouldReceive('recent')
->once()
->andReturn([]);
If you're having a similar issue, examine all the code being tested and make sure you're tests actually fulfill all the requirements/dependancies... or use TDD... something I should have done from the start and avoided this issue.
I'm trying to mock out the Predis client in a PHPUnit test. When I call the method I've tried to mock out, at the end of the test PHPUnit is telling me that the expectation was not met.
Here's a code sample that reproduces my problem:
class MockRedisTest extends \PHPUnit_Framework_TestCase {
private $mockRedis;
public function testMockRedis() {
$mockRedis = $this->getMock('Predis\\Client');
$mockRedis->expects( $this->once())
->method("exists")
->with($this->equalTo("query-key"))
->will($this->returnValue(true));
$mockRedis->exists("query-key");
}
}
And PHPUnit thinks the method wasn't called:
1) MockRedisTest::testMockRedis
Expectation failed for method name is equal to when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
Why? Is it because the Predis client appears to be using __call to respond to method calls that match redis commands?
UPDATE: I get the impression it has something to do with the __call method. Changing the code to this works:
public function testMockRedis() {
$mockRedis = $this->getMock('Predis\\Client');
$mockRedis->expects( $this->once())
->method("__call")
->with("exists", $this->equalTo(array("query-key")))
->will($this->returnValue(true));
$mockRedis->exists("query-key");
}
Not sure I'm satisfied with this though. Is there a better way to mock classes that use __call to proxy methods?
I think you can use
$mockRedis = $this->getMock('Predis\\Client', array('exists'));
// ...
to force the mock object to know about your magic function. This limits to mock's capabilities to the method exists() though. You'll have to specifically include every other method to be mocked.
If you want to mock a specific server profile and make sure you are not calling methods of a different server version, use
<?php
$mockRedis = $this->getMock('Predis\\Client', array_keys((new Predis\Profiles\ServerVersion26)->getSupportedCommands()));
For Phpunit 5, use
$this->createPartialMock('Predis\\Client', ['exists']);
To make your mock know about the "exists" method (or any other redis native command)
I have an interface I want to mock, and mock the behaviour of one of it's methods.
So I have created a callback that mocks the behaviour very simply.
This test passes if I create a new object that is based on this interface, but I would like to mock the interface.
The mocked setUp method is being called fine, and calling getVar('testing') in my callback returns the value. However my assertion fails, because that value isn't available.
It seems that you can't do this in PHPUnit? Unless I am being stupid.
Brief explanation of the code flow; The code in "getVar" calls a method which calls the "setUp" on the added plugin. When it calls "setUp" it passes in "$this". It is $this I am expecting to be passed by reference and which works with a "real" object.
class DefaultRendererTest extends \PHPUnit_Framework_TestCase
{
public function testSetGetVar()
{
$theme = $this->getMock('ThemeInterface');
$plugin = $this->getMock('PluginInterface');
$plugin->expects($this->once())
->method('setUp')
->will($this->returnCallback(function($r){
$r->setVar('testing', "fooBar");
}));
$renderer = new DefaultRenderer($theme, null);
$renderer->addPlugin($plugin);
$this->assertEquals('fooBar',$renderer->getVar('testing'));
}
}
For info here is the interface, the DefaultRenderer implements a RendererInterface
interface PluginInterface
{
function setUp(RendererInterface $renderer);
}
OK, out of interest, I tracked down the issue. It seems that PHPUnit automatically clones the parameters before the actual invocation takes place. I don't see a real reason for this, but maybe there is one. Taking a look at Framework/MockObject/Invocation/Static.php, there is only a single way how you can avoid this (on basis of the built in mock code): Implement a private __clone() method in the DefaultRenderer.
I'd also suggest you ask on IRC or the PHPUnit mailinglist about this behaviour or the mock object library.