如何使用 Laravel Passport 通过 API 测试身份验证?
How to test authentication via API with Laravel Passport?
我正在尝试使用 Laravel 的 Passport 测试身份验证,但没有办法...总是收到 401 客户端无效,我会把我试过的告诉你:
我的php单位配置是来自基地laravel
tests/TestCase.php
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, DatabaseTransactions;
protected $client, $user, $token;
public function setUp()
{
parent::setUp();
$clientRepository = new ClientRepository();
$this->client = $clientRepository->createPersonalAccessClient(
null, 'Test Personal Access Client', '/'
);
DB::table('oauth_personal_access_clients')->insert([
'client_id' => $this->client->id,
'created_at' => date('Y-m-d'),
'updated_at' => date('Y-m-d'),
]);
$this->user = User::create([
'id' => 1,
'name' => 'test',
'lastname' => 'er',
'email' => 'test@test.test',
'password' => bcrypt('secret')
]);
$this->token = $this->user->createToken('TestToken', [])->accessToken;
}
}
tests/Feature/AuthTest.php
class AuthTest extends TestCase
{
use DatabaseMigrations;
public function testShouldSignIn()
{
// Arrange
$body = [
'client_id' => (string) $this->client->id,
'client_secret' => $this->client->secret,
'email' => 'test@test.test',
'password' => 'secret',
];
// Act
$this->json('POST', '/api/signin', $body, ['Accept' => 'application/json'])
// Assert
->assertStatus(200)
->assertJsonStructure([
'data' => [
'jwt' => [
'access_token',
'expires_in',
'token_type',
]
],
'errors'
]);
}
}
我用护照进行方便的身份验证以供测试
routes/api.php
Route::post('/signin', function () {
$args = request()->only(['email', 'password', 'client_id', 'client_secret']);
request()->request->add([
'grant_type' => 'password',
'client_id' => $args['client_id'] ?? env('PASSPORT_CLIENT_ID', ''),
'client_secret' => $args['client_secret'] ?? env('PASSPORT_CLIENT_SECRET', ''),
'username' => $args['email'],
'password' => $args['password'],
'scope' => '*',
]);
$res = Route::dispatch(Request::create('oauth/token', 'POST'));
$data = json_decode($res->getContent());
$isOk = $res->getStatusCode() === 200;
return response()->json([
'data' => $isOk ? [ 'jwt' => $data ] : null,
'errors' => $isOk ? null : [ $data ]
], 200);
});
Laravel Passport actually ships with some testing helpers 可用于测试经过身份验证的 API 端点。
Passport::actingAs(
factory(User::class)->create(),
);
我不熟悉 Dwight 在撰写本文时提到的 Passport 工具,因此这可能是一个更简单的解决方案。但这里有一些可能会有所帮助。它会为您生成一个令牌,然后您可以将其应用于您的 mock-API 调用。
/**
* @param Authenticatable $model
* @param array $scope
* @param bool $personalAccessToken
* @return mixed
*/
public function makeOauthLoginToken(Authenticatable $model = null, array $scope = ['*'], $personalAccessToken = true)
{
$tokenName = $clientName = 'testing';
Artisan::call('passport:client', ['--personal' => true, '--name' => $clientName]);
if (!$personalAccessToken) {
$clientId = app(Client::class)->where('name', $clientName)->first(['id'])->id;
Passport::$personalAccessClient = $clientId;
}
$userId = $model->getKey();
return app(PersonalAccessTokenFactory::class)->make($userId, $tokenName, $scope)->accessToken;
}
然后你只需将它应用到 headers:
$user = app(User::class)->first($testUserId);
$token = $this->makeOauthLoginToken($user);
$headers = ['authorization' => "Bearer $token"];
$server = $this->transformHeadersToServerVars($headers);
$body = $cookies = $files = [];
$response = $this->call($method, $uri, $body, $cookies, $files, $server);
$content = $response->getContent();
$code = $response->getStatusCode();
如果您需要能够解析令牌,试试这个:
/**
* @param string $token
* @param Authenticatable $model
* @return Authenticatable|null
*/
public function parsePassportToken($token, Authenticatable $model = null)
{
if (!$model) {
$provider = config('auth.guards.passport.provider');
$model = config("auth.providers.$provider.model");
$model = app($model);
}
//Passport's token parsing is looking to a bearer token using a protected method. So a dummy-request is needed.
$request = app(Request::class);
$request->headers->add(['authorization' => "Bearer $token"]);
//Laravel\Passport\Guards\TokenGuard::authenticateViaBearerToken() expects the user table to leverage the
//HasApiTokens trait. If that's missing, a query macro can satisfy its expectation for this method.
if (!method_exists($model, 'withAccessToken')) {
Builder::macro('withAccessToken', function ($accessToken) use ($model) {
$model->accessToken = $accessToken;
return $this;
});
/** @var TokenGuard $guard */
$guard = Auth::guard('passport');
return $guard->user($request)->getModel();
}
/** @var TokenGuard $guard */
$guard = Auth::guard('passport');
return $guard->user($request);
}
对于测试通行证,您无需获取真实用户名和密码,您可以创建测试通行证。
您可以使用 Passport::actingAs
或 setup()
.
对于 actingAs
你可以像
public function testServerCreation()
{
Passport::actingAs(
factory(User::class)->create(),
['create-servers']
);
$response = $this->post('/api/create-server');
$response->assertStatus(200);
}
和 setUp()
你可以通过
实现
public function setUp()
{
parent::setUp();
$clientRepository = new ClientRepository();
$client = $clientRepository->createPersonalAccessClient(
null, 'Test Personal Access Client', $this->baseUrl
);
DB::table('oauth_personal_access_clients')->insert([
'client_id' => $client->id,
'created_at' => new DateTime,
'updated_at' => new DateTime,
]);
$this->user = factory(User::class)->create();
$token = $this->user->createToken('TestToken', $this->scopes)->accessToken;
$this->headers['Accept'] = 'application/json';
$this->headers['Authorization'] = 'Bearer '.$token;
}
您可以获得更多详细信息
Here and https://laravel.com/docs/5.6/passport#testing.
这就是实现它的方法,让它真正发挥作用。
首先,您应该正确实施 db:seeds 和 Passport 安装.
第二个,您不需要创建自己的路线来验证是否有效(基本 Passport 响应就足够了)。
下面是关于它在我的安装中如何工作的描述 (Laravel 5.5)...
在我的例子中,我只需要一个 Passport 客户端,这就是为什么我创建了另一条路线,用于 api 授权(api/v1/login
),以仅提供用户名和密码。您可以阅读更多相关信息 here.
幸运的是,这个例子也涵盖了基本的护照授权测试。
所以要成功 运行 你的测试,基本思路是:
- 在测试设置中创建通行密钥。
- 包含用户、角色和其他可能需要的资源的种子数据库。
- 使用
PASSPORT_CLIENT_ID
创建 .env
条目(可选 - Passport 始终在空数据库上创建 ID = 2 的 password grant token
)。
- 使用此 ID 从数据库中获取正确的 client_secret。
- 然后 运行 你的测试...
代码示例...
ApiLoginTest.php
/**
* @group apilogintests
*/
public function testApiLogin() {
$body = [
'username' => 'admin@admin.com',
'password' => 'admin'
];
$this->json('POST','/api/v1/login',$body,['Accept' => 'application/json'])
->assertStatus(200)
->assertJsonStructure(['token_type','expires_in','access_token','refresh_token']);
}
/**
* @group apilogintests
*/
public function testOauthLogin() {
$oauth_client_id = env('PASSPORT_CLIENT_ID');
$oauth_client = OauthClients::findOrFail($oauth_client_id);
$body = [
'username' => 'admin@admin.com',
'password' => 'admin',
'client_id' => $oauth_client_id,
'client_secret' => $oauth_client->secret,
'grant_type' => 'password',
'scope' => '*'
];
$this->json('POST','/oauth/token',$body,['Accept' => 'application/json'])
->assertStatus(200)
->assertJsonStructure(['token_type','expires_in','access_token','refresh_token']);
}
备注:
凭据当然需要修改。
PASSPORT_CLIENT_ID 需要为 2,如前所述。
JsonStructure验证是多余的,因为我们得到200响应,只有授权成功。但是,如果您想要额外的验证,这也可以通过...
TestCase.php
public function setUp() {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
备注:
这里我们正在创建数据库的相关条目,这是我们测试中需要的。
所以请记住,在此处播种具有角色等的用户。
最后的笔记...
这应该足以让您的代码正常工作。在我的系统上,所有这些都通过了绿色,也适用于我的 gitlab CI 运行ner.
最后,请检查路由上的中间件。特别是,如果您正在试验 dingo(或 thymon 的 jwt)包。
唯一的中间件,你可能会考虑,应用到 Passport 授权路由,throttle
有一些保护免受 暴力攻击.
旁注...
Passport 和 dingo 有完全不同的 jwt 实现。
在我的测试中,只有 Passport 的行为是正确的,我认为这就是为什么 dingo 没有被维护的原因没有了。
希望能解决您的问题...
我认为选择的答案可能是迄今为止最可靠和最好的答案,但如果您只需要快速通过 passport 而无需大量设置,我想提供一个对我有用的替代方案。
重要提示:我认为如果你要做很多这样的事情,这不是正确的方法,其他答案会更好。但据我估计,这似乎确实 有效
这是一个完整的测试用例,我需要假设一个用户 POST 到端点,并使用他们的授权令牌发出请求。
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\User;
use Laravel\Passport\Passport;
class MyTest extends TestCase
{
use WithFaker, RefreshDatabase;
public function my_test()
{
/**
*
* Without Artisan call you will get a passport
* "please create a personal access client" error
*/
\Artisan::call('passport:install');
$user = factory(User::class)->create();
Passport::actingAs($user);
//See Below
$token = $user->generateToken();
$headers = [ 'Authorization' => 'Bearer $token'];
$payload = [
//...
];
$response = $this->json('POST', '/api/resource', $payload, $headers);
$response->assertStatus(200)
->assertJson([
//...
]);
}
}
为了清楚起见,这里是 User 模型中的 generateToken()
方法,它利用了 HasApiTokens
特征。
public function generateToken() {
return $this->createToken('my-oauth-client-name')->accessToken;
}
在我看来,这是相当粗糙和准备好的。例如,如果您使用 RefreshDatabase
特征,则必须在每个方法中像这样 运行 passport:install 命令。可能有更好的方法通过全局设置来执行此操作,但我对 PHPUnit 还很陌生,所以这就是我正在做的(目前)。
测试个人访问令牌
这里有一个例子,供任何想使用个人访问令牌测试您的 api 的人使用。
首先,设置测试class
protected function setUp(): void
{
parent::setUp();
$this->actingAs(User::first());
$this->access_token = $this->getAccessToken();
}
至于getAccessToken()
方法,只需使用Passport前端api
private function getAccessToken()
{
$response = $this->post('/oauth/personal-access-tokens',[
'name' => 'temp-test-token'
]);
return $response->json('accessToken');
}
简单地说:
public function the_personal_access_token_allows_us_to_use_the_api()
{
$response = $this->get('/api/user', [
'Authorization' => "Bearer $this->access_token"
]);
$response->assertStatus(200);
}
针对不必要的数据库迁移进行优化
这是一个示例,可确保您仍然能够编写不依赖于数据库的测试 - 而不是 运行 数据库迁移。
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Schema;
use Laravel\Passport\ClientRepository;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
public function setUp(): void
{
parent::setUp();
if (Schema::hasTable('oauth_clients')) {
resolve(ClientRepository::class)->createPersonalAccessClient(
null, config('app.name') . ' Personal Access Client', config('app.url')
);
}
}
}
那么在你的测试中:
...
use RefreshDatabase;
/**
* Test login
*
* @return void
*/
public function test_login()
{
$this->withExceptionHandling();
$user = factory(User::class)->create([
'password' => 'secret'
]);
$response = $this->json('POST', route('api.auth.login'), [
'email' => $user->email,
'password' => 'secret',
]);
$response->assertStatus(200);
$response->assertJsonStructure([
//...
]);
}
...
这样您就可以编写没有任何数据库迁移的测试
我正在尝试使用 Laravel 的 Passport 测试身份验证,但没有办法...总是收到 401 客户端无效,我会把我试过的告诉你:
我的php单位配置是来自基地laravel
tests/TestCase.php
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, DatabaseTransactions;
protected $client, $user, $token;
public function setUp()
{
parent::setUp();
$clientRepository = new ClientRepository();
$this->client = $clientRepository->createPersonalAccessClient(
null, 'Test Personal Access Client', '/'
);
DB::table('oauth_personal_access_clients')->insert([
'client_id' => $this->client->id,
'created_at' => date('Y-m-d'),
'updated_at' => date('Y-m-d'),
]);
$this->user = User::create([
'id' => 1,
'name' => 'test',
'lastname' => 'er',
'email' => 'test@test.test',
'password' => bcrypt('secret')
]);
$this->token = $this->user->createToken('TestToken', [])->accessToken;
}
}
tests/Feature/AuthTest.php
class AuthTest extends TestCase
{
use DatabaseMigrations;
public function testShouldSignIn()
{
// Arrange
$body = [
'client_id' => (string) $this->client->id,
'client_secret' => $this->client->secret,
'email' => 'test@test.test',
'password' => 'secret',
];
// Act
$this->json('POST', '/api/signin', $body, ['Accept' => 'application/json'])
// Assert
->assertStatus(200)
->assertJsonStructure([
'data' => [
'jwt' => [
'access_token',
'expires_in',
'token_type',
]
],
'errors'
]);
}
}
我用护照进行方便的身份验证以供测试
routes/api.php
Route::post('/signin', function () {
$args = request()->only(['email', 'password', 'client_id', 'client_secret']);
request()->request->add([
'grant_type' => 'password',
'client_id' => $args['client_id'] ?? env('PASSPORT_CLIENT_ID', ''),
'client_secret' => $args['client_secret'] ?? env('PASSPORT_CLIENT_SECRET', ''),
'username' => $args['email'],
'password' => $args['password'],
'scope' => '*',
]);
$res = Route::dispatch(Request::create('oauth/token', 'POST'));
$data = json_decode($res->getContent());
$isOk = $res->getStatusCode() === 200;
return response()->json([
'data' => $isOk ? [ 'jwt' => $data ] : null,
'errors' => $isOk ? null : [ $data ]
], 200);
});
Laravel Passport actually ships with some testing helpers 可用于测试经过身份验证的 API 端点。
Passport::actingAs(
factory(User::class)->create(),
);
我不熟悉 Dwight 在撰写本文时提到的 Passport 工具,因此这可能是一个更简单的解决方案。但这里有一些可能会有所帮助。它会为您生成一个令牌,然后您可以将其应用于您的 mock-API 调用。
/**
* @param Authenticatable $model
* @param array $scope
* @param bool $personalAccessToken
* @return mixed
*/
public function makeOauthLoginToken(Authenticatable $model = null, array $scope = ['*'], $personalAccessToken = true)
{
$tokenName = $clientName = 'testing';
Artisan::call('passport:client', ['--personal' => true, '--name' => $clientName]);
if (!$personalAccessToken) {
$clientId = app(Client::class)->where('name', $clientName)->first(['id'])->id;
Passport::$personalAccessClient = $clientId;
}
$userId = $model->getKey();
return app(PersonalAccessTokenFactory::class)->make($userId, $tokenName, $scope)->accessToken;
}
然后你只需将它应用到 headers:
$user = app(User::class)->first($testUserId);
$token = $this->makeOauthLoginToken($user);
$headers = ['authorization' => "Bearer $token"];
$server = $this->transformHeadersToServerVars($headers);
$body = $cookies = $files = [];
$response = $this->call($method, $uri, $body, $cookies, $files, $server);
$content = $response->getContent();
$code = $response->getStatusCode();
如果您需要能够解析令牌,试试这个:
/**
* @param string $token
* @param Authenticatable $model
* @return Authenticatable|null
*/
public function parsePassportToken($token, Authenticatable $model = null)
{
if (!$model) {
$provider = config('auth.guards.passport.provider');
$model = config("auth.providers.$provider.model");
$model = app($model);
}
//Passport's token parsing is looking to a bearer token using a protected method. So a dummy-request is needed.
$request = app(Request::class);
$request->headers->add(['authorization' => "Bearer $token"]);
//Laravel\Passport\Guards\TokenGuard::authenticateViaBearerToken() expects the user table to leverage the
//HasApiTokens trait. If that's missing, a query macro can satisfy its expectation for this method.
if (!method_exists($model, 'withAccessToken')) {
Builder::macro('withAccessToken', function ($accessToken) use ($model) {
$model->accessToken = $accessToken;
return $this;
});
/** @var TokenGuard $guard */
$guard = Auth::guard('passport');
return $guard->user($request)->getModel();
}
/** @var TokenGuard $guard */
$guard = Auth::guard('passport');
return $guard->user($request);
}
对于测试通行证,您无需获取真实用户名和密码,您可以创建测试通行证。
您可以使用 Passport::actingAs
或 setup()
.
对于 actingAs
你可以像
public function testServerCreation()
{
Passport::actingAs(
factory(User::class)->create(),
['create-servers']
);
$response = $this->post('/api/create-server');
$response->assertStatus(200);
}
和 setUp()
你可以通过
public function setUp()
{
parent::setUp();
$clientRepository = new ClientRepository();
$client = $clientRepository->createPersonalAccessClient(
null, 'Test Personal Access Client', $this->baseUrl
);
DB::table('oauth_personal_access_clients')->insert([
'client_id' => $client->id,
'created_at' => new DateTime,
'updated_at' => new DateTime,
]);
$this->user = factory(User::class)->create();
$token = $this->user->createToken('TestToken', $this->scopes)->accessToken;
$this->headers['Accept'] = 'application/json';
$this->headers['Authorization'] = 'Bearer '.$token;
}
您可以获得更多详细信息 Here and https://laravel.com/docs/5.6/passport#testing.
这就是实现它的方法,让它真正发挥作用。
首先,您应该正确实施 db:seeds 和 Passport 安装.
第二个,您不需要创建自己的路线来验证是否有效(基本 Passport 响应就足够了)。
下面是关于它在我的安装中如何工作的描述 (Laravel 5.5)...
在我的例子中,我只需要一个 Passport 客户端,这就是为什么我创建了另一条路线,用于 api 授权(api/v1/login
),以仅提供用户名和密码。您可以阅读更多相关信息 here.
幸运的是,这个例子也涵盖了基本的护照授权测试。
所以要成功 运行 你的测试,基本思路是:
- 在测试设置中创建通行密钥。
- 包含用户、角色和其他可能需要的资源的种子数据库。
- 使用
PASSPORT_CLIENT_ID
创建.env
条目(可选 - Passport 始终在空数据库上创建 ID = 2 的password grant token
)。 - 使用此 ID 从数据库中获取正确的 client_secret。
- 然后 运行 你的测试...
代码示例...
ApiLoginTest.php
/**
* @group apilogintests
*/
public function testApiLogin() {
$body = [
'username' => 'admin@admin.com',
'password' => 'admin'
];
$this->json('POST','/api/v1/login',$body,['Accept' => 'application/json'])
->assertStatus(200)
->assertJsonStructure(['token_type','expires_in','access_token','refresh_token']);
}
/**
* @group apilogintests
*/
public function testOauthLogin() {
$oauth_client_id = env('PASSPORT_CLIENT_ID');
$oauth_client = OauthClients::findOrFail($oauth_client_id);
$body = [
'username' => 'admin@admin.com',
'password' => 'admin',
'client_id' => $oauth_client_id,
'client_secret' => $oauth_client->secret,
'grant_type' => 'password',
'scope' => '*'
];
$this->json('POST','/oauth/token',$body,['Accept' => 'application/json'])
->assertStatus(200)
->assertJsonStructure(['token_type','expires_in','access_token','refresh_token']);
}
备注:
凭据当然需要修改。
PASSPORT_CLIENT_ID 需要为 2,如前所述。
JsonStructure验证是多余的,因为我们得到200响应,只有授权成功。但是,如果您想要额外的验证,这也可以通过...
TestCase.php
public function setUp() {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
备注:
这里我们正在创建数据库的相关条目,这是我们测试中需要的。 所以请记住,在此处播种具有角色等的用户。
最后的笔记...
这应该足以让您的代码正常工作。在我的系统上,所有这些都通过了绿色,也适用于我的 gitlab CI 运行ner.
最后,请检查路由上的中间件。特别是,如果您正在试验 dingo(或 thymon 的 jwt)包。
唯一的中间件,你可能会考虑,应用到 Passport 授权路由,throttle
有一些保护免受 暴力攻击.
旁注...
Passport 和 dingo 有完全不同的 jwt 实现。
在我的测试中,只有 Passport 的行为是正确的,我认为这就是为什么 dingo 没有被维护的原因没有了。
希望能解决您的问题...
我认为选择的答案可能是迄今为止最可靠和最好的答案,但如果您只需要快速通过 passport 而无需大量设置,我想提供一个对我有用的替代方案。
重要提示:我认为如果你要做很多这样的事情,这不是正确的方法,其他答案会更好。但据我估计,这似乎确实 有效
这是一个完整的测试用例,我需要假设一个用户 POST 到端点,并使用他们的授权令牌发出请求。
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\User;
use Laravel\Passport\Passport;
class MyTest extends TestCase
{
use WithFaker, RefreshDatabase;
public function my_test()
{
/**
*
* Without Artisan call you will get a passport
* "please create a personal access client" error
*/
\Artisan::call('passport:install');
$user = factory(User::class)->create();
Passport::actingAs($user);
//See Below
$token = $user->generateToken();
$headers = [ 'Authorization' => 'Bearer $token'];
$payload = [
//...
];
$response = $this->json('POST', '/api/resource', $payload, $headers);
$response->assertStatus(200)
->assertJson([
//...
]);
}
}
为了清楚起见,这里是 User 模型中的 generateToken()
方法,它利用了 HasApiTokens
特征。
public function generateToken() {
return $this->createToken('my-oauth-client-name')->accessToken;
}
在我看来,这是相当粗糙和准备好的。例如,如果您使用 RefreshDatabase
特征,则必须在每个方法中像这样 运行 passport:install 命令。可能有更好的方法通过全局设置来执行此操作,但我对 PHPUnit 还很陌生,所以这就是我正在做的(目前)。
测试个人访问令牌
这里有一个例子,供任何想使用个人访问令牌测试您的 api 的人使用。
首先,设置测试class
protected function setUp(): void
{
parent::setUp();
$this->actingAs(User::first());
$this->access_token = $this->getAccessToken();
}
至于getAccessToken()
方法,只需使用Passport前端api
private function getAccessToken()
{
$response = $this->post('/oauth/personal-access-tokens',[
'name' => 'temp-test-token'
]);
return $response->json('accessToken');
}
简单地说:
public function the_personal_access_token_allows_us_to_use_the_api()
{
$response = $this->get('/api/user', [
'Authorization' => "Bearer $this->access_token"
]);
$response->assertStatus(200);
}
针对不必要的数据库迁移进行优化
这是一个示例,可确保您仍然能够编写不依赖于数据库的测试 - 而不是 运行 数据库迁移。
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Schema;
use Laravel\Passport\ClientRepository;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
public function setUp(): void
{
parent::setUp();
if (Schema::hasTable('oauth_clients')) {
resolve(ClientRepository::class)->createPersonalAccessClient(
null, config('app.name') . ' Personal Access Client', config('app.url')
);
}
}
}
那么在你的测试中:
...
use RefreshDatabase;
/**
* Test login
*
* @return void
*/
public function test_login()
{
$this->withExceptionHandling();
$user = factory(User::class)->create([
'password' => 'secret'
]);
$response = $this->json('POST', route('api.auth.login'), [
'email' => $user->email,
'password' => 'secret',
]);
$response->assertStatus(200);
$response->assertJsonStructure([
//...
]);
}
...
这样您就可以编写没有任何数据库迁移的测试