I have a line of code in a Laravel 5 event handler which looks like this:
$this->event->batch->increment('attempted_jobs');
$this->event is the event which calls the handler and $this->event->batch contains my Batch model. All this does in increment the attempted_jobs column within my database, so it's fairly basic stuff.
I would like to be able to test this event handler, I'm using Codeception and Mockery. My mock for $this->event->batch looks like this:
$batch = m::mock('MyVendor\MyApp\Batch');
$batch->shouldReceive('increment')->once()->with('attempted_jobs');
This however causes issues - increment() is a protected method of Model and therefore cannot be mocked. Here's the exact error:
InvalidArgumentException: increment() cannot be mocked as it a protected method and mocking protected methods is not allowed for this mock
It appears to be implemented using the __call() PHP magic method, so how to I mock this? I've attempted creating a __call() mock, but this churns out tonnes of errors related to the increment() method not being implemented.
The issue was because, as stated, increment() is a protected method of Illuminate\Database\Eloquent\Model(). The way to get around this is to mock the __call() method directly, like so:
$batch = m::mock('MyVendor\MyApp\Batch');
$batch->shouldReceive('__call')->with('increment')->once();
(I'm not sure why this didn't work when I first tried it though)
Related
I like to test (Feature test via phpunit) some methods in the Job Class (Lumen Queue) to make sure it is working correctly.
Problem is Job Class have some jobs methods like $this->job->getJobId() and $this->release(5);
If I run phpunit from console, I get an error:
Error: Call to a member function getJobId() on null
The test code look like this:
/**
* #test
*/
public function it_has_successfully_uploaded()
{
$job = new SomeJob(['file' => ['file1.zip']]);
$job->handle();
}
How do I solve this issue?
Your code is using $this->job->getJobId(), but nowhere is that property declared or set. Are you perhaps using the InteractsWithQueue trait but forgot to include that in your code paste?
If so, the job property is set in InteractsWithQueue::setJob. This method may be called from several places, but in your case it is probably from Illuminate\Queue\CallQueuedHandler::setJobInstanceIfNecessary. This is some internal initialization of your job that Laravel does for you, which you must imitate in your test setup.
I would implement a simplistic version of Illuminate\Contracts\Queue\Job and call $job->setJob(new SimplisticVersionShazaamJob(...)); before calling $job->handle().
I'm trying this:
$this->dsMock = Mockery::mock('Eloquent', 'API\V1\DataSet');
$this->app->instance('API\V1\DataSet', $this->dsMock);
$this->dsMock->shouldReceive('isLocalData')->once()->andReturn(true);
Then, inside the class under test:
$test = DataSet::isLocalData($dataSetId);
However, the DataSet class is not being mocked. It's still trying to access the database. Why?
The likely problem is Laravel's unfortunate over use of Façade's (which are also factories). If DataSet was already instantiated using the Façade, it will keep returning the same class, you won't get a mocked version.
I can't remember off hand if you can instantiate your class without using the Façade in Laravel. You have to remember that when you call DataSet statically in the application, you're actually not referencing API\V1\DataSet but something else that manages it.
I'm trying to understand Tests and Mockery a bit more with Laravel. I have a repository pattern setup, which my controller users. I want to test my basic getAllUsers()method:
public function test_get_all_users_method()
{
$repo = Mockery::mock('Acme\Repositories\User\UserRepository');
$repo->shouldReceive('all')->once()->andReturn('foo');
$controller = new Acme\Controllers\Api\UserController($repo);
$response = $controller->getComponents();
$this->assertEquals('foo', $response);
}
As I understand it, I'm mocking my UserRepository, and I expect my UserRepository to have it's all() method hit. This returns some dummy data and I expect to see this in my response output.
So that works fine. The all() method exists in my Eloquent implementation of the repository. However, if I remove the all() method, the test still passes... Why would it? Surely the test should fail.
If this is normal, I'm struggling to understand why I'd test my controller like this, since I could pass any old method name into it even if it exists or not.
Cheers
That's how mockery operates by default, I like it that way because it allows me to develop by wishful thinking, i.e. I wish my UserRepository interface had an all method.
You can tell mockery to disallow it though, it's a bit ugly, but you can put this in your test bootstrap file:
\Mockery::getConfiguration()->allowMockingNonExistentMethods(false);
You could also set this up to control it with an environment variable or something, so you allow mocking non-existent methods during normal use, but prevent it on your continuous integration run etc.
I am beginning to write tests using Laravel 4.2, PHPUnit, and Mockery.
How can I assert that a Mock Facade was called/run by the Controller that I am testing?
For example, I've got the following Mockery for Queue. My controller doesn't return anything that has to do with the Queue::push(), so is there a way I can assert that it triggered Queue::push()?
Queue::shouldReceive('push')->once();
Queue::shouldReceive('connected')->once();
If I comment out the Queue::push() in my controller, the Queue::shouldReceive throws an exception:
Mockery\Exception\InvalidCountException: Method push() from Mockery_0_Illuminate_Queue_QueueManager should be called exactly 1
times but called 0 times.
Is the only way to assert that the Mock Facade ran is to do something like the following?
$queuepushran = false;
try {
Queue::shouldReceive('push')->once();
Queue::shouldReceive('connected')->once();
$queuepushran = true;
} catch (\Mockery\Exception\InvalidCountException $e) {
//Queue threw an exception
$queuepushran = false;
}
$this->assertTrue($queuepushran);
You're on the right track with using mock objects and expectations to test this, but this isn't quite how mocks should be used. Your code snippet seems to be missing actually calling the controller, so it's hard to tell exactly what's going on, but I'll just run over a full example.
You want to test that when your controller method runs, the Queue facade's push() and connected() methods are called once each. What we want to do is use Laravel's cool facade functionality to replace the real QueueManager class behind the Queue facade with a mock one, that way when the controller calls Queue::push(..), it will be calling our mock object instead.
So, lets start building the test:
public function testSomeControllerMethodCallsTheQueue()
{
Queue::shouldReceive('push')->once();
Queue::shouldReceive('connected')->once();
}
We've now swapped out the real QueueManager for our mock one, and set up how we expect them to be called. Since each test should only be doing one thing, we've now done all we need to do to 'arrange' the test.
Next, we need to 'act' by calling the controller.
public function testSomeControllerMethodCallsTheQueue()
{
Queue::shouldReceive('push')->once();
Queue::shouldReceive('connected')->once();
$this->action('GET', 'YourController#method');
}
The last step is to 'assert', but we don't actually need to do anything. If our Mockery expectations are met, the test will complete and go green. If our Mockery expectations are not met, an exception will be thrown and the test will go red. The exceptions that Mockery throws are intended to be what fails your test, you don't need to do assertions like when comparing the return value or response with what you expect -- Mockery handles all that.
I am trying to test a class that manages data access in the database (you know, CRUD, essentially). The DB library we're using happens to have an API wherein you first get the table object by a static call:
function getFoo($id) {
$MyTableRepresentation = DB_DataObject::factory("mytable");
$MyTableRepresentation->get($id);
... do some stuff
return $somedata
}
...you get the idea.
We're trying to test this method, but mocking the DataObject stuff so that (a) we don't need an actual db connection for the test, and (b) we don't even need to include the DB_DataObject lib for the test.
However, in PHPUnit I can't seem to get $this->getMock() to appropriately set up a static call. I have...
$DB_DataObject = $this->getMock('DB_DataObject', array('factory'));
...but the test still says unknown method "factory". I know it's creating the object, because before it said it couldn't find DB_DataObject. Now it can. But, no method?
What I really want to do is to have two mock objects, one for the table object returned as well. So, not only do I need to specify that factory is a static call, but also that it returns some specified other mock object that I've already set up.
I should mention as a caveat that I did this in SimpleTest a while ago (can't find the code) and it worked fine.
What gives?
[UPDATE]
I am starting to grasp that it has something to do with expects()
I agree with both of you that it would be better not to use a static call. However, I guess I forgot to mention that DB_DataObject is a third party library, and the static call is their best practice for their code usage, not ours. There are other ways to use their objects that involve constructing the returned object directly. It just leaves those darned include/require statements in whatever class file is using that DB_DO class. That sucks because the tests will break (or just not be isolated) if you're meanwhile trying to mock a class of the same name in your test--at least I think.
When you cannot alter the library, alter your access of it. Refactor all calls to DB_DataObject::factory() to an instance method in your code:
function getFoo($id) {
$MyTableRepresentation = $this->getTable("mytable");
$MyTableRepresentation->get($id);
... do some stuff
return $somedata
}
function getTable($table) {
return DB_DataObject::factory($table);
}
Now you can use a partial mock of the class you're testing and have getTable() return a mock table object.
function testMyTable() {
$dao = $this->getMock('MyTableDao', array('getMock'));
$table = $this->getMock('DB_DataObject', ...);
$dao->expects($this->any())
->method('getTable')
->with('mytable')
->will($this->returnValue($table));
$table->expects...
...test...
}
This is a good example of a dependency in your code - the design has made it impossible to inject in a Mock rather than the real class.
My first suggestion would be to try and refactor the code to use an instance rather than a static call.
What's missing (or not?) from your DB_DataObject class is a setter to pass a prepared db object before calling the factory method. That way you can pass a mock or a custom db object (with the same interface) should the need arise.
In your test setup:
public function setUp() {
$mockDb = new MockDb();
DB_DataObject::setAdapter($mockDb);
}
The factory() method should return the mocked DB instance. If it's not already integrated into your class, you will probably have to refactor the factory() method as well to make it work.
Are you require/including the class file for DB_DataObject in your test case? If the class doesn't exist before PHPUnit tries to mock the object you can get errors like this.
With PHPUnit MockFunction extension plus runkit you can also mock static methods. Be careful, because it's monkey patching and therefore should only be used in extreme cases. Does not substitute good programming practices.
https://github.com/tcz/phpunit-mockfunction