Laravel - 如何使用 mockery 对更新用户数据的中间件进行单元测试
Posted
技术标签:
【中文标题】Laravel - 如何使用 mockery 对更新用户数据的中间件进行单元测试【英文标题】:Laravel - How to unit test a middleware updating user's data using mockery 【发布时间】:2021-03-26 02:28:32 【问题描述】:我有以下中间件来更新用户的 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
。但是,我不明白这个问题能够解决它。
【问题讨论】:
我将使用功能测试对其进行测试。基本上,在您的测试中创建一个分配了中间件的路由,访问该路由,在登录的用户上声明更新的last_seen
检查这篇文章laravel-news.com/testing-laravel-middleware
@ml59 所以你说不可能进行单元测试?以前的开发人员为他们实现的大多数中间件编写了单元测试。我只是想保持一致。
我从不这么说。 Laravel 社区中有很多关于如何测试Middleware
、Request
等的讨论。有些使用单元测试,有些使用功能测试。如果你想进行单元测试,你将不得不模拟很多东西,编写复杂的代码,可读性较差。我建议您进行功能测试,因为它更容易。在文章(上面的链接)中,您可以从框架的创建者那里找到有关实践单元与功能的更多信息
@ml59 呃,我发现了问题。这有点愚蠢。我自己发布了一个答案。这并不复杂。我想知道你对此的看法。我的测试可以吗?
【参考方案1】:
好吧,这个问题从一开始就很愚蠢。我错过了传递$user
作为模拟Request
的user
方法的返回值。以下测试通过:
// 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);
【讨论】:
【参考方案2】:归根结底,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);
【讨论】:
您正在创建一个真实的用户和用户身份验证。这对单元测试来说不是很糟糕吗?据我了解,对于单元测试,必须隔离一个单元。我的理解错了吗? 你的回答引起了我的注意。为什么要通过容器获取中间件而不直接实例化呢?有什么好处吗? 使用容器获取新实例是一个好习惯。它将自动解决依赖关系。在那种中间件的情况下,你可以让new LastSeen
得到相同的结果
你对我的第一条评论的回答是什么?谢谢。
在单元测试中,我们希望测试一个特定的方法或断言一个值。重点不是测试整个功能。这就是我正在做的事情。您可以模拟或使用工厂。就个人而言,我不喜欢嘲笑所有东西,并且使测试的可读性降低,并且难以为相同的结果进行调试。只需使用更快的方式以上是关于Laravel - 如何使用 mockery 对更新用户数据的中间件进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章
在 Laravel 4 中使用 Mockery 模拟自定义类
Laravel 5 - 使用 Mockery 模拟 Eloquent 模型