I'm currently writing tests for a package in Laravel 4.
I am mocking the Illuminate\Database\Query\Builder which works nearly all the time except when a where method uses a callback, I cannot check if the methods inside the callback are called.
I was hoping one of you could shed some light.
$query = \Mockery::mock('Illuminate\Database\Query\Builder', function ($mock) {
/** #var \Mockery\Mock $mock */
$mock->shouldReceive('where');
$mock->shouldReceive('orWhere')->twice();
});
And the actual where method that should call orWhere. Note: That builder mock gets passed to the class below.
$builder = new LaravelBuilder($query);
Which then calls $builder->filter() which contains the below code.
$this->query->where(function ($query) use ($filterData) {
/** #var \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query */
foreach($filterData['columns'] as $colData) {
/** #var \Samvaughton\Ldt\Column $column */
$column = $colData['column'];
// See if this column is searchable
if (!$column->isSearchable() || !$colData['searchable']) continue;
// If the individual column term is empty, use the main term
$term = (empty($colData['term'])) ? $filterData['term'] : $colData['term'];
// Actually apply the filter
$query->orWhere($column->getSqlColumn(), "LIKE", "%{$term}%");
}
});
The main part is the bottom bit $query->orWhere, the PHPUnit tests fail every time as it doesn't run the orWhere even once. Before you say that it might not reach the execution of it due to continue, the data I am passing will allow this.
I suspect this is due to how I am mocking the where method in the first place. If I include an exit before the foreach it does not execute indicating that it doesn't even run anything inside the callback. I know that is the default behaviour but how do I get Mockery to run the same/similar callback?
I have tried using partial mocks, using shouldExpect but don't fully understand it. I've also tried searching around but no luck with this scenario.
Would be great if I could learn how to use mockery within callbacks.
OK so I think this can be done a better way, but I resorted to partially mocking Laravel's query builder.
See line 324 in Illuminate\Database\Query\Builder
When using a closure Laravel spawns a new query builder which also needs to be mocked. Here's the code that worked:
$query = \Mockery::mock('Illuminate\Database\Query\Builder', function ($mock) {
/** #var \Mockery\Mock $mock */
$mock->makePartial();
$mock->shouldReceive('where')->once()->passthru();
$mock->shouldReceive('newQuery')->andReturn(
\Mockery::mock('Illuminate\Database\Query\Builder', function ($mock) {
/** #var \Mockery\Mock $mock */
$mock->makePartial();
$mock->shouldReceive('orWhere')->twice();
})
);
});
I needed to use makePartial() so the query builder retained its original functionality for closures (which is not true to unit testing) so this isn't the best solution. From here I mock the newQuery method which is called to spawn a new query builder instance which I then mock in a similar format.
Related
I'm doing unit testing and my repository has this method:
/**
* #param int|Collection $id
* #return MyModel|Collection|null
*/
public function find($id);
I tried this:
$this->package_repo = Mockery::mock(PackageRepositoryInterface::class);
$this->app->instance('Package', $this->package_repo);
// ...
$this->ticket_repo->shouldReceive("find")->with(collect([]))->andReturn(/*...*/);
//...
echo $this->ticket_repo->find(collect([])); // Failed here.
The test immediately failed, and I think it's because the 2 collections in the shouldReceive expectation statement and the actual find statement are distinct objects.
If I want to mock the behavior of $this->ticket_repo->find($ids), what should I do?
Or rather, if there is a better way to design a testable algorithm, how would it be? Because I would like to have all the ids to I want to find be in an array, and be constructed into a single SQL select query, instead of a few hundred separate ones, for performance purposes.
Also asked here: https://laracasts.com/discuss/channels/testing/mocking-findcollection-in-repository-pattern-in-laravel-with-mockery#
You have to mock a concrete class and not the interface. Since you haven't provided the name I'm just guessing:
$mock = Mockery::mock(TicketRepo::class);
$this->app->swap('PackageRepo', $mock);
So now whenever someone asks for an instance by the Interface name they'll get the mock.
I've been successfully using Mockery with PHPUnit tests lately. Yet, there is a dependency in a project I'm currently working that uses static method calls to interact with an API. I'm struggling to test one particular use case and it feels like I'll find other like this during the development roadmap.
Using this class as an example:
namespace Name\Space;
class User
{
/**
* #return \Name\Space\User[]
*/
public static function list(): array
{
// ...
}
public static function create(array $attrs): User
{
// ...
}
}
In case I just want to assert a method returns a primitive type, such as an array:
Mockery::mock('alias:\Name\Space\User')
->shouldReceive('list')
->andReturn([]);
It works fine, primarily because I'm not testing the array contents.
However, I have to call the create method, which returns an instance of the class itself (User). If I do something like this:
$user = new \Name\Space\User();
Mockery::mock('alias:\Name\Space\User')
->shouldReceive('create')
->andReturn($user);
The alias, obviously, won't work because the class was already loaded through the autoloader (composer's, in this case).
Does anyone have a suggestion on how to workaround this?
What about creating User in a closure?
<?php
$user = Mockery::mock('overload:\Name\Space\User')
->shouldReceive('create')
->andReturnUsing(function() {
return new \Name\Space\User();
});
Mocking static stuff is always painful.
I would recommend creating a Proxy object that is calling the static API calls and just returns the API results and inject this object everywhere you need to call the API.
This way it is easy to test by simply mocking the proxy object.
The proxy object itself can then be tested in an end to end test outside of the pure unit test scope.
You can still do more invasive stuff like this
https://www.pagemachine.de/blog/mocking-static-method-calls/?cn-reloaded=1
But writing code that doesn't belong to your unit tests purely to make something testable doesn't feel right to me.
I have a method that uses another class to calculate its outcome, that I want to test using PHPUnit.
/**
* Returns true if the given user has been granted the given permission.
*
* #param User $user
* #param AbstractPermission $permission
* #return bool
*/
public function userPermissionGranted(User $user, AbstractPermission $permission) : bool
{
// Retrieve model from database.
$user_permission = UserPermission::scopeUser($user)
->scopePermission($permission)
->first();
return $user_permission ? $user_permission->isGranted() : $permission->isGrantedByDefault();
}
Leaving out of consideration what this method actually does, I am wondering how to test this method. I can pass mocks of the User and AbstractPermission classes to the method, but the UserPermission class that is used inside the method's body (to retrieve a model from the database) I can do nothing with.
On top of that, if I pass mocks of the User and Permisson classes, they won't exist in the database, so when UserPermission queries the database, it will receive no results and the method will fail.
What do I do here? Is it considered good practice to simply mock the database (i.e. copying the live db structure and filling it with test data) and let my model query that database, and just trusting that everything is OK? Any suggestions on what to do here?
On a side note, UserPermission is an Eloquent model. I am merely making use of Eloquent here - without Laravel.
As a general rule, you can't directly mock static methods - at least, there's no good way to do it. Depending on how your application is set up, you might be able to hack something together that involves redefining the method with runkit or perhaps messing with includes/autoloader to load a mock class instead of the real one, but such solutions are kludgey at best.
One simple approach to allow unit-testing would be to wrap your static method calls in an instance method. So you'd create a new class with instance methods that call the static methods. Of course, you wouldn't be able to test that new class, but if it's a thin wrapper around the static methods then there's not really any value in testing it anyway.
So you might end up with something like this, for example:
class UserPermissionWrapper {
public function getUserPermission($user) {
return UserPermission::scopeUser($user);
}
}
Then you can inject that into your original class and get something like this:
public function userPermissionGranted(User $user, AbstractPermission $permission) : bool
{
// Assume this is an instance of UserPermissionWrapper injected at construction
$user_permission = $this->userPermissionWrapper
->getUserPermission($user)
->scopePermission($permission)
->first();
return $user_permission ? $user_permission->isGranted() : $permission->isGrantedByDefault();
}
Now you have an object calling instance methods, so you can inject a mock version of that class and set up the method calls in the normal way.
To answer my own question - and I've only come to a reasonable answer after a while of writing some more unit tests - I guess what it comes down to is this:
When testing the userPermissionGranted() method, we're actually only validating that the method works as expected. We're fetching a model from the database, and we may assume that this model has been tested already in its own, separate test. Given that we may assume that this model works as intended, and that we cannot access the database here to fetch the actual model, we can use a mock of the model, which we customly build to work just the way it should work, without actually performing any database work. That's where Peter Geer's answer comes in. Our class should contain a method to fetch the model from the database, so that instead of fetching and returning the model from the database, we can set up a mock and return that instead. In this case that means that in the return line of the method we're testing, we're going to test a mocked isGranted() on $user_permission (which is the mock we created to return a value that we want it to return), and isGrantedByDefault() on the $permission mock that we passed to the method when we called it.
I'm new to testing and writing testable code, and am looking for some clarification on the correct way to handle this simple scenario. I've read other questions and answers on SO with similar titles but they do not seem to offer a clear answer to what I'm asking.
I have a controller that calls the shipped() method on an instance of my Picking class:
class MyController extends \BaseController {
public function controllerMethod() {
$picking = new Picking;
$picking->shipped($shipmentData);
}
}
The Picking model looks like this:
class Picking extends \Eloquent {
public function order() {
return $this->belongsTo('Order');
}
public function shipped($shipmentData) {
$this->carrier = $shipmentData['Carrier'];
$this->service = $shipmentData['Service'];
$this->is_shipped = true;
$this->save();
$this->order->pickingShipped();
}
}
As you can see, this shipped() method saves some data, and then calls the pickingShipped() method, on it's related Order.
Now, I am trying to write a test for the shipped() method, and I'm not sure the appropriate way to do this. I've read about mocking, but I am confused if this is a situation where mocking is necessary. I've thought of a few possible solutions, but I'm not sure if any of them are correct.
1) Rearrange the code so that the controller calls the pickingShipped() method allowing it to be removed from the shipped() method, simplifying the test.
For example, the last line of the shipped() method would be removed, and the controller code would change to:
$picking = new Picking;
$picking->shipped($shipmentData);
$picking->order->pickingShipped();
2) In the test, use a mock method on order so that the test can simply confirm that the pickingShipped() method gets called.
Something along the lines of what's explained here. That would mean the test could do something like this:
$order->expects($this->once())->method('pickingShipped')
However, I think that would mean that I also need to inject the order dependency rather than relying on the order relationship within the shipped() method, like this:
class Picking extends \Eloquent {
public function order() {
return $this->belongsTo('Order');
}
public function shipped(Order $order, $shipmentData) {
$this->carrier = $shipmentData['Carrier'];
$this->service = $shipmentData['Service'];
$this->is_shipped = true;
$this->save();
$order->pickingShipped();
}
}
And then the code in the controller would have to look like this:
$picking = new Picking;
$picking->shipped($picking->order, $shipmentData);
This feels a little strange, but I'm really not sure what's right.
My question is, what is the proper way to write and test this code? It's easy to test the the shipped() method sets the appropriate data on itself, but what about that call to pickingShipped() at the end? This seems to make the testing more complicated. So should the code be rearranged? If so, how? Or, is this a common use-case for mocking like I outlined in the 2nd option? If so, is it correct to inject the dependency as I'm showing?
I'm not a PHP dev so this might come down to language features being a blocker.
I would suggest that the dependency injection method is better because it calls out the dependency and would allow you to separate your persistence and behavior later. For instance the Picking or Picker might be a better behavior name whilst PickingRecord might be nice for the data.
In any case if you can set default arguments in PHP then I like the last method you used (injection) and you could currently simplify to something like
public function shipped($shipmentData, Order $order = $this->order) {
$this->carrier = $shipmentData['Carrier'];
$this->service = $shipmentData['Service'];
$this->is_shipped = true;
$this->save();
$order->pickingShipped();
}
This then would allow you to ignore the order dependency in production code and inject a double or other type of object as an order in tests and simply assert that the method was called on the order object. Integration tests should continue to monitor that the interfaces still mesh together even though you're injecting doubles in your unit tests.
This would be how I'd attempt to do this in Ruby.
I came up with a solution that I feel good about. It seems pretty obvious now that I see it. All I did was set the $picking->order property to return the mocked order for the test.
$order = Mockery::mock(Order::class);
$picking = new Picking;
$picking->order = $order;
$order->shouldReceive('pickingShipped')
->with($picking)
->once();
$picking->shipped($shipmentData);
Now when the shipped() method calls $this->order, it gets the mocked $order object I defined, and the test works correctly.
This feels like the right solution.
I am running CakePHP 2.8.X, and am trying to write a unit test for a Model function.
Let's call the model Item, and I'm trying to test its getStatus method.
However, that model makes a call to its find within the getStatus method.
So something like this:
class Item extends Model
{
public function getStatus($id) {
// Calls our `$this->Item-find` method
$item = $this->find('first', [
'fields' => ['status'],
'conditions' => ['Item.id' => $id]
]);
$status = $item['status'];
$new_status = null;
// Some logic below sets `$new_status` based on `$status`
// ...
return $new_status;
}
}
The logic to set "$new_status" is a bit complex, which is why I want to write some tests for it.
However, I'm not entirely sure how to override the find call within Item::getStatus.
Normally when I want to mock a Model's function, I use $this->getMock coupled with method('find')->will($this->returnValue($val_here)), but I don't want to completely mock my Item since I want to test its actual getStatus function.
That is, in my test function, I'm going to be calling:
// This doesn't work since `$this->Item->getStatus` calls out to
// `$this->Item->find`, which my test suite doesn't know how to compute.
$returned_status = $this->Item->getStatus($id);
$this->assertEquals($expected_status, $returned_status);
So how do I communicate to my real Item model within my test that it should override its internal call to its find method?
I knew this had to be an issue others have faced, and it turns out PHPUnit has a very easy way to address this!
This tutorial essentially gave me the answer.
I do need to create a mock, but by only passing in 'find' as the methods I'd like to mock, PHPUnit helpfully leaves all other methods in my Model alone and does not override them.
The relevant part from the above tutorial is:
Passing an array of method names to your getMock second argument produces a mock object where the methods you have identified
Are all stubs,
All return null by default,
Are easily overridable
Whereas methods you did not identify
Are all mocks,
Run the actual code contained within the method when called (emphasis mine),
Do not allow you to override the return value
Meaning, I can take that mocked model, and call my getStatus method directly from it. That method will run its real code, and when it gets to find(), it'll just return whatever I passed into $this->returnValue.
I use a dataProvider to pass in what I want the find method to return, as well as the result to test against in my assertEquals call.
So my test function looks something like:
/**
* #dataProvider provideGetItemStatus
*/
public function testGetItemStatus($item, $status_to_test) {
// Only mock the `find` method, leave all other methods as is
$item_model = $this->getMock('Item', ['find']);
// Override our `find` method (should only be called once)
$item_model
->expects($this->once())
->method('find')
->will($this->returnValue($item));
// Call `getStatus` from our mocked model.
//
// The key part here is I am only mocking the `find` method,
// so when I call `$item_model->getStatus` it is actually
// going to run the real `getStatus` code. The only method
// that will return an overridden value is `find`.
//
// NOTE: the param for `getStatus` doesn't matter since I only use it in my `find` call, which I'm overriding
$result = $item_model->getStatus('dummy_id');
$this->assertEquals($status_to_test, $result);
}
public function provideGetItemStatus() {
return [
[
// $item
['Item' => ['id' = 1, 'status' => 1, /* etc. */]],
// status_to_test
1
],
// etc...
];
}
one way to mock find could be to use a test specific subclass.
You could create a TestItem that extends item and overrides find so it doesn't perform a db call.
Another way could be to encapsulate the new_status logic and unittests it independent of the model