测试你的 REST APIs
在完成每个控制器后,您都会通过发出请求并期待响应来测试 REST API。正如您所想象的那样,这有时会很方便,但这肯定不是正确的做法。测试应该是自动的,并应尽可能多地覆盖。我们必须考虑一种类似于单元测试的解决方案。
在第 10 章 "行为测试 "中,你将学习到更多端到端测试应用程序的方法和工具,其中包括 REST API。不过,由于 REST API 很简单,我们也可以利用 Laravel 提供的工具添加一些很好的测试。实际上,这个想法与我们在第 8 章 "使用现有的 PHP 框架 "中编写的测试非常相似,都是向某个端点发出请求,并期望得到响应。唯一不同的是我们使用的断言类型(可以检查 JSON 响应是否正常)和执行请求的方式。
让我们为与图书相关的端点集添加一些测试。我们需要数据库中的一些书籍才能对其进行查询,因此我们必须在每次测试前填充数据库,即使用 setUp 方法。请记住,为了使数据库中没有测试数据,我们需要使用 DatabaseTransactions 特质。在 tests/BooksTest.php 中添加以下代码:
<?php
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Book;
class BooksTest extends TestCase {
use DatabaseTransactions;
private $books = [];
public function setUp() {
parent::setUp();
$this->addBooks();
}
private function addBooks() {
$this->books[0] = Book::create(
[
'isbn' => '293842983648273',
'title' => 'Iliad',
'author' => 'Homer',
'stock' => 12,
'price' => 7.40
]
);
$this->books[0]->save();
$this->books[0] = $this->books[0]->fresh();
$this->books[1] = Book::create(
[
'isbn' => '9879287342342',
'title' => 'Odyssey',
'author' => 'Homer',
'stock' => 8,
'price' => 10.60
]
);
$this->books[1]->save();
$this->books[1] = $this->books[1]->fresh();
$this->books[2] = Book::create(
[
'isbn' => '312312314235324',
'title' => 'The Illuminati',
'author' => 'Larry Burkett',
'stock' => 22,
'price' => 5.10
]
);
$this->books[2]->save();
$this->books[2] = $this->books[2]->fresh();
}
}
正如您在前面的代码中所看到的,我们在数据库中添加了三本书,并在类属性 $books 中也添加了三本书。当我们断言响应有效时,就会用到它们。还请注意 fresh 方法的使用;该方法将我们拥有的模型与数据库中的内容同步。我们需要这样做才能在数据库中插入 ID,因为我们事先并不知道它。
在运行每个测试之前,我们还需要做另一件事:验证客户端。我们需要向访问令牌生成端点发出 POST 请求,发送有效凭证,并存储收到的访问令牌,以便在其余请求中使用。你可以自由选择如何提供凭据,因为有不同的方法。在我们的例子中,我们只提供了一个已知存在于数据库中的客户端测试的凭据,但你可能更喜欢每次都将该客户端插入数据库。用以下代码更新测试:
<?php
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Book;
class BooksTest extends TestCase {
use DatabaseTransactions;
private $books = [];
private $accessToken;
public function setUp() {
parent::setUp();
$this->addBooks();
$this->authenticate();
}
//...
private function authenticate() {
$this->post(
'oauth/access_token',
[
'client_id' => 'iTh4Mzl0EAPn90sK4EhAmVEXS',
'client_secret' => 'PfoWM9yq4Bh6rhr8oDDsNZM',
'grant_type' => 'client_credentials'
]
);
$response = json_decode(
$this->response->getContent(), true
);
$this->accessToken = $response['access_token'];
}
}
在前面的代码中,我们使用 post
方法发送 POST 请求。该方法接受一个包含端点的字符串和一个包含参数的数组。发出请求后,Laravel 会将响应对象保存到 $response 属性中。我们可以对其进行 JSON 解码,并提取所需的访问令牌。
是时候添加一些测试了。让我们从一个简单的测试开始:给定一个 ID 请求一本书。ID 用于使用图书的 ID 进行 GET 请求(不要忘记访问令牌),并检查响应是否与预期的一致。请记住,我们已经有了 $books
数组,因此执行这些检查非常容易。
我们将使用两个断言:seekJson,用于比较接收到的 JSON 响应和我们提供的响应;assertResponseOk,我们已经在之前的测试中了解过,它只是检查响应是否有 200 状态代码。将此测试添加到类中:
public function testGetBook() {
$expectedResponse = [
'book' => json_decode($this->books[1], true)
];
$url = 'books/' . $this->books[1]->id
. '?' . $this->getCredentials();
$this->get($url)
->seeJson($expectedResponse)
->assertResponseOk();
}
private function getCredentials(): string {
return 'grant_access=client_credentials&access_token='
. $this->accessToken;
}
我们使用 get 方法而不是 post 方法,因为这是一个 GET 请求。另外请注意,我们使用了 getCredentials 助手,因为我们必须在每个测试中使用它。要查看另一个示例,让我们添加一个测试,在请求包含给定标题的图书时检查响应:
public function testGetBooksByTitle() {
$expectedResponse = [
'books' => [
json_decode($this->books[0], true),
json_decode($this->books[2], true)
]
];
$url = 'books/?title=Il&' . $this->getCredentials();
$this->get($url)
->seeJson($expectedResponse)
->assertResponseOk();
}
前面的测试与前面的测试基本相同,不是吗?唯一的变化是端点和预期响应。其余的测试都将遵循相同的模式,因为到目前为止,我们只能获取图书并对其进行过滤。
为了看到不同的东西,让我们来看看如何测试创建资源的端点。有几种不同的方法,其中一种是先发出请求,然后到数据库检查资源是否已创建。另一种方法,也是我们更喜欢的方法,是首先发送创建资源的请求,然后根据响应中的信息,发送请求以获取新创建的资源。这种方法更可取,因为我们只测试 REST API,不需要知道数据库使用的具体模式。此外,如果 REST API 更改了数据库,测试也会继续通过,而且应该继续通过,因为我们只通过接口进行测试。
借书就是一个很好的例子。测试应首先发送 POST 借书请求,指定图书 ID,然后从响应中提取借书 ID,最后发送 GET 请求,询问借书情况。为了节省时间,你可以将下面的测试添加到已有的 tests/BooksTest.php 中:
public function testBorrowBook() {
$params = ['book-id' => $this->books[1]->id];
$params = array_merge($params, $this->postCredentials());
$this->post('borrowed-books', $params)
->seeJsonContains(['book_id' => $this->books[1]->id])
->assertResponseOk();
$response = json_decode($this->response->getContent(), true);
$url = 'borrowed-books' . '?' . $this->getCredentials();
$this->get($url)
->seeJsonContains(['id' => $response['borrowed-book']['id']])
->assertResponseOk();
}
private function postCredentials(): array {
return [
'grant_access' => 'client_credentials',
'access_token' => $this->accessToken
];
}