daveb2's avatar

Livewire testing and mocking

I'm trying to write a test case for an inline Livewire component, eg. not a route-based Livewire component (the route is served by regular model resource mapping).

So far I have:

Livewire::actingAs($user)->test(\App\Http\Livewire\Survey\Edit::class)
			->set('date', $data['date'])
			->call('save')
			->assertHasNoErrors();

I have a valid user in $user. \App\Http\Livewire\Survey\Edit::class is the name of my Livewire component. It is rendered in a blade view like so:

<livewire:survey.edit/>

All pretty standard stuff. But when I run the test case from the command line:

php artisan test tests/Feature/SurveyCrudTest.php

I get an error from an included blade component:

Cannot assign null to property App\View\Components\Form\Button::$currentSection of type string

Because I tried to assign something to the property with:

$this->currentSection = \Route::currentRouteName();

Because obviously \Route:: doesn't exist in the test context. So I am trying to mock \Route::currentRouteName() and \Route::getRoutes() like so:

		\Route::shouldReceive('currentRouteName')
			->once()
			->withNoArgs()
			->andReturn(route('surveys.create', [$client->id]));

		\Route::shouldReceive('getRoutes')
			->once()
			->withNoArgs()
			->andReturn(new \Illuminate\Routing\RouteCollection());

However, I keep receiving the error:

  Mockery\Exception\BadMethodCallException

  Received Mockery_0_Illuminate_Routing_Router::getRoutes(), but no expectations were specified

  at vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php:57
     53▕      */
     54▕     protected function registerUrlGenerator()
     55▕     {
     56▕         $this->app->singleton('url', function ($app) {
  ➜  57▕             $routes = $app['router']->getRoutes();
     58▕
     59▕             // The URL generator needs the route collection that exists on the router.
     60▕             // Keep in mind this is an object, so we're passing by references here
     61▕             // and all the registered routes will be available to the generator.

I cannot work out what it means by "no expectations were specified". Does anyone know what is going on here?

0 likes
5 replies
LaryAI's avatar
Level 58

The error message "Received Mockery_0_Illuminate_Routing_Router::getRoutes(), but no expectations were specified" suggests that the test is not properly setting up the mock for the \Route::getRoutes() method. One possible solution is to use the shouldReceive method to set the expectation for the getRoutes method before calling the Livewire component. Here's an example:

use Illuminate\Routing\RouteCollection;
use Illuminate\Support\Facades\Route;
use Livewire\Livewire;

// ...

public function testInlineLivewireComponent()
{
    // Set up the mock for Route::getRoutes()
    Route::shouldReceive('getRoutes')
        ->once()
        ->withNoArgs()
        ->andReturn(new RouteCollection());

    // Set up the mock for Route::currentRouteName()
    Route::shouldReceive('currentRouteName')
        ->once()
        ->withNoArgs()
        ->andReturn('surveys.create');

    // Call the Livewire component
    Livewire::actingAs($user)
        ->test(\App\Http\Livewire\Survey\Edit::class)
        ->set('date', $data['date'])
        ->call('save')
        ->assertHasNoErrors();
}

In this example, we first set up the mock for Route::getRoutes() using the shouldReceive method. We specify that the method should be called once with no arguments, and should return a new RouteCollection instance. We then set up the mock for Route::currentRouteName() in a similar way.

Finally, we call the Livewire component using the Livewire::actingAs()->test() method chain, and then call the set, call, and assertHasNoErrors methods as before.

By setting up the mock for Route::getRoutes() before calling the Livewire component, we ensure that the Livewire component can access the routes it needs to function properly.

daveb2's avatar

This is exactly the same as my code? The order of shouldReceives is different but I still get the same error

daveb2's avatar

OK I have boiled my question down to: How can I mock \Route::currentRouteName() ?

I have tried:

\Route::shouldReceive('currentRouteName')
			->once()
			->andReturn(route('surveys.create'));

as well as:

$app = app();// laravel app instance

// mock some Router:: static methods
$routerMock = Mockery::mock($app->make(\Illuminate\Routing\Router::class));
$routerMock->shouldReceive('currentRouteName')->andReturn(route('surveys.create'));

$app->instance('router', $routerMock);

In each case calling \Route::currentRouteName() inside the tested component is returning null.

ian_h's avatar

@daveb2 I think you have a mix-up here of functions/methods/expectations.

For example:

Route::currentRouteName();

would return the likes of:

surveys.create

However:

route('surveys.create');

would return a URL for that route name, eg:

https://www.foo.bar/surveys/create

With this in mind, calling the currentRouteName() method won't give you the URL (which I suspect is what you're actually after as you're attempting to return route('surveys.create') from the mock?)

If you're looking to get the URL from the route name in a test, you can use:

$surveysCreateUrl = app('url')->route('surveys.create');

Does this help?

Just for completeness of mocking things as per your original thoughts:

Route::partialMock()
    ->expects('currentRouteName')
    ->once()
    ->andReturns('surveys.create');
$routeName = Route::currentRouteName();

would return:

surveys.create

Cheers..

Ian

daveb2's avatar

@ian_h Thanks Ian, I think I have the difference between route name and URL now. But I will keep working on understanding how mocks work as I can't get the call to $this->currentSection = \Route::currentRouteName(); inside the livewire component to return a value (always null), even though I'm doing:

\Route::shouldReceive('currentRouteName')
			->once()
			->withNoArgs()
			->andReturn(route('surveys.create', [$client->id]));

before I run the test with

Livewire::actingAs($user)->test(Edit::class)
			->set($data)
			->call('save')
			->assertHasNoErrors();

Please or to participate in this conversation.