Testing Laravel View Composers with Mockery - laravel

I am trying to test my View Composers. Whenever I pass an object to the $view->with('string', $object), my test fails. This is when I do the test like this:
$view
->shouldReceive('with')
->with('favorites', $this->user->favorites(Ad::class)->get())
->once();
I'm pretty sure this is due to strict checking. So I looked around and saw this issue. However, I can't seem to get it working. The closure return true, but the test fails:
Mockery\Exception\InvalidCountException : Method with('favorites',
< Closure===true >) from Mockery_3_Illuminate_View_View should be called
exactly 1 times but called 0 times.
Here is my current test
public function it_passes_favorites_to_the_view()
{
$this->setUpUser(); // basically sets $this->user to a User object
Auth::shouldReceive('user')
->once()
->andReturn($this->user);
$composer = new FavoritesComposer();
$view = Mockery::spy(View::class);
$view
->shouldReceive('with')
->with('favorites', Mockery::on(function($arg) {
$this->assertEquals($this->user->favorites(Ad::class)->get(), $arg);
}))
->once();
$composer->compose($view);
}
FavoritesComposer class:
public function compose(View $view)
{
$user = Auth::user();
$favorites = $user
? $user->favorites(Ad::class)->get()
: collect([]);
$view->with('favorites', $favorites);
}
How do I test object like this?

I fixed the issue by replacing $view->with('favorites', $favorites); with $view->with(['favorites' => $favorites]); and then testing it like this:
$view
->shouldReceive('with')
->with(['favorites' => $this->user->favorites(Ad::class)->get()])
->once();
So, essentially using only one parameter in the with()-method is what fixed it for me.

Related

Laravel partial mock doesn't work with Eloquent model

I got this error when I try to partially mock an Eloquent model:
Received Mockery_1_App_Models_Invoice::__construct(), but no expectations were specified
I only want to mock the method "getPdf" of my model.
My service
class MyService
{
public function sendPdf($invoiceId)
{
// Invoice::class extend: Illuminate\Database\Eloquent\Model
$invoiceRepository = Container::getInstance()->make(Invoice::class);
$invoice = $invoiceRepository->find($invoiceId);
$invoice->getPdf();
}
}
The test
it('a test', function () {
$filePath = '/path-of-a-pdf.pdf';
$this->partialMock(Invoice::class, function (MockInterface $mock) use ($filePath) {
$mock->shouldReceive('getPdf')->atLeast()->once()->andReturn($filePath);
});
$invoice = Invoice::factory()->create([
'type' => 'invoice'
]);
$myService = new MyService();
$myService->sendPdf($invoice->id);
});
If I try with a full mock, it works but I don't want to mock everything like the method "find".
Do you have an idea of this problem?
I found this issue but I don't really find a solution to this problem.
Thank you in advance for your help.

Unit Testing with Model::Find Fails in Laravel

I am creating unit tests for my rather large Laravel application.
This test passes all assertions. It creates a new database entry for the advertising status model.
/** #test **/
public function create_a_new_advertising_status_test()
{
$status = AdvertisingStatus::create(['name' => 'Test']);
$this->assertNotNull($status);
$this->assertCount(1, AdvertisingStatus::all());
$this->assertEquals($status->name, $this->advertisingStatus['name']);
}
This test fails the NotNull assertion as it says $status is null, which means that the Find method is failing.
/** #test **/
public function edit_an_existing_advertising_status_entry()
{
$status = AdvertisingStatus::create(['name' => 'Test']);
// $status = AdvertisingStatus::first(); // This successfully finds the database entry
$status = AdvertisingStatus::find(1); // This fails to find the database entry
$status->name = 'Edit';
$status->save();
$this->assertNotNull($status);
$this->assertCount(1, AdvertisingStatus::all());
$this->assertEquals($status->name, "Edit");
}
It appears like the find function and subsequently the where function takes too long to locate the entry, so the test renders the $status variable null.
Aside from using the Model::first() function, does anyone have any idea how to overcome this?
I'm wondering if it's because my application is extremely large and takes a long time to run because I'm using RefreshDatabase
You have to create a factory first, then you will use it like this:
$status = factory(AdvertisingStatus::class)->create(['name' => 'Test']);

Testing Laravel (5.1) console commands with phpunit

What is the best way to test Laravel console commands?
Here is an example of a command I'm running. It takes in a value in the constructor and in the handle method.
class DoSomething extends Command
{
protected $signature = 'app:do-something';
protected $description = 'Does something';
public function __construct(A $a)
{
...
}
public function handle(B $b)
{
...
}
}
In my test class, I can mock both A and B, but I can't figure out how to pass $a in.
$this->artisan('app:do-something', [$b]);
Is it possible? Or am I going about this all wrong? Should I pass everything in thought the handle() method?
Thanks.
You will have to change around how you call the command in testing, but it is possible to mock an object passed through.
If the class used by Artisan is dependency-injected like this:
public function __construct(ActualObject $mocked_A)
{
//
}
Then write up the test case like this:
$mocked_A = Mockery::mock('ActualObject');
$this->app->instance('ActualObject', $mocked_A);
$kernel = $this->app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArrayInput([
'command' => 'app:do-something',
]),
$output = new Symfony\Component\Console\Output\BufferedOutput
);
$console_output = $output->fetch();
The $this->app->instance('ActualObject', $mocked_A); line is where you are able to call upon and use the mocked version of your class, or object, instead of the actual.
This will work in Laravel or Lumen.

Adding methods to Eloquent Model in Laravel

I'm a bit confused how I am to add methods to Eloquent models. Here is the code in my controller:
public function show($id)
{
$limit = Input::get('limit', false);
try {
if ($this->isExpand('posts')) {
$user = User::with(['posts' => function($query) {
$query->active()->ordered();
}])->findByIdOrUsernameOrFail($id);
} else {
$user = User::findByIdOrUsernameOrFail($id);
}
$userTransformed = $this->userTransformer->transform($user);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound('User does not exist');
}
return $this->respond([
'item' => $userTransformed
]);
}
And the code in the User model:
public static function findByIdOrUsernameOrFail($id, $columns = array('*')) {
if (is_int($id)) return static::findOrFail($id, $columns);
if ( ! is_null($user = static::whereUsername($id)->first($columns))) {
return $user;
}
throw new ModelNotFoundException;
}
So essentially I'm trying to allow the user to be retrieved by either user_id or username. I want to preserve the power of findOrFail() by creating my own method which checks the $id for an int or string.
When I am retrieving the User alone, it works with no problem. When I expand the posts then I get the error:
Call to undefined method
Illuminate\Database\Query\Builder::findByIdOrUsernameOrFail()
I'm not sure how I would go about approaching this problem.
You are trying to call your method in a static and a non-static context, which won't work. To accomplish what you want without duplicating code, you can make use of Query Scopes.
public function scopeFindByIdOrUsernameOrFail($query, $id, $columns = array('*')) {
if (is_int($id)) return $query->findOrFail($id, $columns);
if ( ! is_null($user = $query->whereUsername($id)->first($columns))) {
return $user;
}
throw new ModelNotFoundException;
}
You can use it exactly in the way you are trying to now.
Also, you can use firstOrFail:
public function scopeFindByIdOrUsernameOrFail($query, $id, $columns = array('*')) {
if (is_int($id)) return $query->findOrFail($id, $columns);
return $query->whereUsername($id)->firstOrFail($columns);
}
Your method is fine, but you're trying to use it in two conflicting ways. The one that works as you intended is the one in the else clause, like you realised.
The reason the first mention doesn't work is because of two things:
You wrote the method as a static method, meaning that you don't call it on an instantiated object. In other words: User::someStaticMethod() works, but $user->someStaticMethod() doesn't.
The code User::with(...) returns an Eloquent query Builder object. This object can't call your static method.
Unfortunately, you'll either have to duplicate the functionality or circumvent it someway. Personally, I'd probably create a user repository with a non-static method to chain from. Another option is to create a static method on the User model that starts the chaining and calls the static method from there.
Edit: Lukas's suggestion of using a scope is of course by far the best option. I did not consider that it would work in this situation.

Laravel 4 : Route to localhost/controller/action

I'm more or less new to Laravel 4. I've never used routes before but normally what I'm used to is url/controller/action and then the backend routing for me. I've read the documentation for routes and controllers a few times as well as read through some tutorials and so, I'm trying to figure out how to get this to work without writing a route for every controller and action.
I tried something like
Route::get('{controller}/{action}', function($controller, $action = 'index'){
return $controller."#".$action;
});
Now then, I know this is wrong since it doesn't work, but what am I missing? On most tutorials and stuff I'm seeing an route for more or less every controller and action like:
Route::get('/controller/action' , 'ControllerName#Action');
Which seems silly and like a waste of time to me.
Is there anyway to achieve what I want?
If you are looking for a more automated routing, this would be the Laravel 4 way:
Route:
Route::controller('users', 'UsersController');
Controller (in this case UsersController.php):
public function getIndex()
{
// routed from GET request to /users
}
public function getProfile()
{
// routed from GET request to /users/profile
}
public function postProfile()
{
// routed from POST request to /users/profile
}
public function getPosts($id)
{
// routed from GET request to: /users/posts/42
}
As The Shift Exchange mentioned, there are some benefits to doing it the verbose way. In addition to the excellent article he linked, you can create a name for each route, for example:
Route::get("users", array(
"as"=>"dashboard",
"uses"=>"UsersController#getIndex"
));
Then when creating urls in your application, use a helper to generate a link to a named route:
$url = URL::route('dashboard');
Links are then future proofed from changes to controllers/actions.
You can also generate links directly to actions which would still work with automatic routing.
$url = URL::action('UsersController#getIndex');
app\
controllers\
Admin\
AdminController.php
IndexController.php
Route::get('/admin/{controller?}/{action?}', function($controller='Index', $action='index'){
$controller = ucfirst($controller);
$action = $action . 'Action';
return App::make("Admin\\{$controller}Controller")->$action();
});
Route::get('/{controller?}/{action?}', function($controller='Index', $action='index'){
$controller = ucfirst($controller);
$action = $action . 'Action';
return App::make("{$controller}Controller")->$action();
});
I come from .Net world and routing is typically done:
/{Controller}/{action}/{id}
Which looks like:
/Products/Show/1 OR /Products/Show/Beverages
In Laravel I accomplish this routing like so:
Route::get('/{controller?}/{action?}/{id?}', function ($controller='Home', $action='index', $id = null) {
$controller = ucfirst($controller);
return APP::make("{$controller}Controller")->$action($id);
});
The controller would look roughly like so:
class ProductsController extends BaseController {
public function Show($id) {
$products = array( 1 => array("Price" => "$600","Item" => "iPhone 6"),
2 => array("Price" => "$700", "Item" => "iPhone 6 Plus") );
if ($id == null) {
echo $products[1]["Item"];
} else {
echo $products[$id]["Item"];
}
}
}

Resources