如何在Laravel测试中禁用选择的中间件

25

在运行phpunit时,认证和CSRF错误经常出现。

在TestCase中我们使用:

use WithoutMiddleware;

问题在于当表单提交失败时,通常会返回一个闪存消息和旧输入。我们已经禁用了所有中间件,因此无法访问Input::old('username');或者闪存消息。

此外,我们对这个失败的表单提交进行测试的结果是:

Caused by
exception 'RuntimeException' with message 'Session store not set on request.

有没有一种方法可以启用Session Middleware并禁用其他所有内容?


你可能还想看看这个:https://github.com/laravel/internals/issues/506#issuecomment-291552399 - ira
6个回答

33

我发现最好的方法不是使用WithoutMiddleware特性,而是修改您想要禁用的中间件。例如,如果您想在测试中禁用VerifyCsrfToken中间件功能,则可以执行以下操作:

app/Http/Middleware/VerifyCsrfToken.php中,添加一个handle方法来检查APP_ENV是否为testing。

public function handle($request, Closure $next)
{
    if (env('APP_ENV') === 'testing') {
        return $next($request);
    }

    return parent::handle($request, $next);
}

这将覆盖Illuminate\Foundation\Http\Middleware\VerifyCsrfToken中的handle方法,完全禁用其功能。


3
不错的解决方案,易于阅读和理解。顺便提一句,env('APP_ENV') 可以用 app()->env 来代替,这样会更好一点 :) - Limon Monte
1
谢谢。如果有人想要覆盖 VerifyCSRF 处理方法并且你得到了一个 Argument 2 needs to be of type App\Http\Middleware\Closure 的错误,请查看这个答案 - tread
8
不要直接检查env('APP_ENV'),可以使用app()->runningUnitTests()代替(它在内部执行相同的评估,但如果将来更改了环境值仍将起作用)。 - Miquel Correa Casablanca

30

Laravel >= 5.5

从 Laravel 5.5 开始,withoutMiddleware() 方法允许你指定要禁用的中间件,而不是禁用所有中间件。因此,你可以在测试中执行以下操作,而无需修改所有中间件以添加 env 检查:

$this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);

Laravel < 5.5

如果您使用的是 Laravel < 5.5 版本,您可以将更新方法添加到基础 TestCase 类中以覆盖框架 TestCase 的功能,从而实现相同的功能。

PHP >= 7

如果您使用的是 PHP7+ 版本,则在 TestCase 类中添加以下内容,即可使用上述提到的相同方法调用。此功能使用了 PHP7 中引入的匿名类。

/**
 * Disable middleware for the test.
 *
 * @param  string|array|null  $middleware
 * @return $this
 */
public function withoutMiddleware($middleware = null)
{
    if (is_null($middleware)) {
        $this->app->instance('middleware.disable', true);

        return $this;
    }

    foreach ((array) $middleware as $abstract) {
        $this->app->instance($abstract, new class {
            public function handle($request, $next)
            {
                return $next($request);
            }
        });
    }

    return $this;
}

PHP < 7

如果你使用的是 PHP < 7,你需要创建一个真正的类文件,并将其注入容器而不是匿名类。

在某个地方创建这个类:

class FakeMiddleware
{
    public function handle($request, $next)
    {
        return $next($request);
    }
}

在您的TestCase中覆盖withoutMiddleware()方法并使用您的FakeMiddleware类:

/**
 * Disable middleware for the test.
 *
 * @param  string|array|null  $middleware
 * @return $this
 */
public function withoutMiddleware($middleware = null)
{
    if (is_null($middleware)) {
        $this->app->instance('middleware.disable', true);

        return $this;
    }

    foreach ((array) $middleware as $abstract) {
        $this->app->instance($abstract, new FakeMiddleware());
    }

    return $this;
}

2
我刚刚浪费了30分钟的时间,试图弄清楚为什么尽管我在测试中添加了 $this->withoutMiddleware('friendly-alias-name'); ,但特定的中间件仍在运行。结果发现,你必须使用中间件类名而不是友好别名。 - kolaente

2
你可以在测试中使用trait:

使用 Illuminate\Foundation\Testing\WithoutMiddleware;

Laravel版本需大于等于5.7。
"Original Answer" 翻译成 "最初的回答"

1
我可能会迟到,但我已经想出来了:
$this->withoutMiddleware([
   'email-verified', //alias does NOT work
   EnsureEmailIsVerified::class //Qualified class name DOES WORK
]);

0

0
以下方法适用于我:
use WithoutMiddleware;

public function setUp(): void
{
    parent::setUp();
    $this->withoutMiddleware();
}

它已经存在很长时间了,那你为什么需要调用这个方法呢?https://github.com/laravel/framework/blob/9.x/src/Illuminate/Foundation/Testing/TestCase.php#L134 https://github.com/laravel/framework/blob/9.x/src/Illuminate/Foundation/Testing/WithoutMiddleware.php - ssi-anik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接