Laravel - 如何使用 mockery 对更新用户数据的中间件进行单元测试

Laravel - How to unit test a middleware updating user's data using mockery

我有以下中间件来更新用户的 last_seen_at 字段,我只将它用于经过身份验证的用户可以调用它的路由:

// app/Http/Middleware/LastSeen.php

public function handle($request, Closure $next)
{
    $user = $request->user();

    $user->last_seen_at = now();

    $user->save();

    return $next($request);
}
// app/Http/Kernel.php

protected $routeMiddleware = [
    // other middleware
    'lastseen' => \App\Http\Middleware\LastSeen::class
]
// routes/api.php

Route::group([
    'middleware' => ['jwt', 'lastseen']
], function () {
    // routes
});

我编写了以下用于测试中间件的单元测试:

// tests/Unit/LastSeenMiddlewareTest.php

/** @test */
public function doesLastSeenMiddlewareUpdateUsersLastSeenAt()
{
    $user = Mockery::mock(User::class);

    $user->shouldReceive('setAttribute')->passthru();
    $user->shouldReceive('save')->once();

    $user = $user->makePartial();

    app()->instance(User::class, $user);

    $request = Mockery::mock(Request::class)
        ->shouldReceive('user')
        ->once()
        ->andReturn()
        ->getMock();

    $middleware = new LastSeen;

    $middleware->handle($request, function () {});
}

通过运行测试,我得到一个错误:ErrorException: Creating default object from empty value。但是,我不明白能够解决它的问题。

好吧,这个问题从一开始就很愚蠢。我错过了将 $user 作为模拟 Requestuser 方法的 return 值传递。以下测试通过:

// tests/Unit/LastSeenMiddlewareTest.php

/** @test */
public function doesLastSeenMiddlewareUpdateUsersLastSeenAt()
{
    $user = Mockery::mock(User::class);

    /* As I'm making a partial mock,
       I don't need to worry about passing
       through the call to the setAttribute method */

    $user->shouldReceive('save')->once();

    $user = $user->makePartial();

    app()->instance(User::class, $user);

    $request = Mockery::mock(Request::class)
        ->shouldReceive('user')
        ->once()
        ->andReturn($user) // I should've passed $user here
        ->getMock();

    $middleware = new LastSeen;

    $middleware->handle($request, function () {});

    $this->assertEqualsWithDelta($user->last_seen_at, now(), 1);
}

归根结底,middlaware 只是一个带有 handle 方法的经典 class
我将这样进行单元测试:

  public function doesLastSeenMiddlewareUpdateUsersLastSeenAt()
    {
        //note: this is using the old factory syntax prior to Laravel 8 https://github.com/laravel/legacy-factories
        $user = factory(User::class)->create(['last_seen_at' => now()->subHour()]);
        
        //login as the user since in your middleware you get the logged user
        $this->actingAs($user);
        
        //get the middleware through the container
        $middleware = app(LastSeen::class);
        
        //call handle method
        $middleware->handle(request(),  function () {});
        $this->assertEqualsWithDelta($user->refresh()->last_seen_at, now(), 1);
    }