如何使用 PHPUnit 在 Laravel 上测试更复杂的案例 [英] How to feature test more complicated cases on Laravel using PHPUnit
问题描述
我在我的项目中使用 Laravel,而且我是单元/功能测试的新手,所以我想知道在编写测试时处理更复杂的功能用例的最佳方法是什么?
I'm using Laravel for my project and I'm new to unit/feature testing so I was wondering what is the best way to approach more complicated feature cases when writing tests?
我们以这个测试为例:
// tests/Feature/UserConnectionsTest.php
public function testSucceedIfConnectAuthorised()
{
$connection = factory(Connection::class)->make([
'sender_id' => 1,
'receiver_id' => 2,
'accepted' => false,
'connection_id' => 5,
]);
$user = factory(User::class)->make([
'id' => 1,
]);
$response = $this->actingAs($user)->post(
'/app/connection-request/accept',
[
'accept' => true,
'request_id' => $connection->id,
]
);
$response->assertLocation('/')->assertStatus(200);
}
所以我们遇到了这种情况,我们在两个用户之间有一些连接系统.由其中一位用户创建的数据库中有一个 Connection
条目.现在要使其成功连接,第二个用户必须批准它.问题在于 UserController
通过 connectionRequest
接受这个:
So we've got this situation where we have some connection system between two users. There is a Connection
entry in the DB created by one of the users. Now to make it a successful connection the second user has to approve it. The problem is within the UserController
accepting this through connectionRequest
:
// app/Http/Controllers/Frontend/UserController.php
public function connectionRequest(Request $request)
{
// we check if the user isn't trying to accept the connection
// that he initiated himself
$connection = $this->repository->GetConnectionById($request->get('request_id'));
$receiver_id = $connection->receiver_id;
$current_user_id = auth()->user()->id;
if ($receiver_id !== $current_user_id) {
abort(403);
}
[...]
}
// app/Http/Repositories/Frontend/UserRepository.php
public function GetConnectionById($id)
{
return Connection::where('id', $id)->first();
}
所以我们在测试函数中得到了这个假的(工厂创建的)连接,然后我们不幸的是使用它的假 id 在真实数据库中的真实连接中运行检查,这不是我们想要的:(
So we've got this fake (factory created) connection in the test function and then we unfortunately are using its fake id to run a check within the real DB among real connections, which is not what we want :(
研究我发现了创建接口的想法,这样我们就可以根据我们是否正在测试提供不同的方法体.就像这里的 GetConnectionById()
一样,可以很容易地伪造测试用例的答案.这看起来不错,但是:
Researching I found an idea of creating interfaces so then we can provide a different method bodies depending if we're testing or not. Like here for GetConnectionById()
making it easy to fake answers to for the testing case. And that seems OK, but:
- 对于一个人来说,这看起来像是一种开销,除了编写测试之外,我还必须使真实的".仅出于测试目的,代码本身就更复杂了.
- 第二件事,我阅读了 Laravel 文档中关于测试的所有内容,并且没有一个地方提到使用接口,所以我也想知道这是否是解决这个问题的唯一方法和最佳方法问题.
推荐答案
我会尽力帮助你,当有人开始 testing 这一点都不容易,特别是如果你没有一个强大的框架(甚至是一个框架).
I will try to help you, when someone start with testing it is not easy at all, specially if you don't have a strong framework (or even a framework at all).
那么,让我来帮你吧:
- 区分单元测试和功能测试非常重要.您正确地使用了功能测试,因为您想直接测试业务逻辑而不是类.
- 在您进行测试时,我个人的建议始终是创建第二个数据库以仅用于测试.它必须一直是完全空的.
- 因此,要实现这一点,您必须在
phpunit.xml
中定义正确的环境变量,这样当您只运行测试时就不必为此做任何魔术. - 另外,使用
RefreshDatabase
特征.因此,每次运行测试时,它都会删除所有内容,再次迁移表并运行测试.
- It is really important to differentiate Unit testing vs Feature testing. You are correctly using Feature test, because you want to test business logic instead of a class directly.
- When you test, my personal recommendation is always create a second DB to only use with tests. It must be completely empty all the time.
- So, for you to achieve this, you have to define the correct environment variables in
phpunit.xml
, so you don't have to do magic for this to work when you only run tests. - Also, use
RefreshDatabase
trait. So, each time you run a test, it is going to delete everything, migrate your tables again and run the test.
所以,为了帮助你,我假设你有一个清晰的数据库(没有表的数据库,
RefreshDatabase
trait 会为你处理这个).So, to help you in your case, I will assume you have a clear database (database without tables,
RefreshDatabase
trait will handle this for you).我会让你的第一个测试是这样的:
I would have your first test as this:
public function testShouldSucceedWhenConnectAuthorised() { /** * I have no idea how your relations are, but I hope * you get the main idea with this. Just create what * you should expect to have when you have this * test case */ $connection = factory(Connection::class)->create([ 'sender_id' => factory(Sender::class)->create()->id, 'receiver_id' => factory(Reciever::class)->create()->id, 'accepted' => false, 'connection_id' => factory(Connection::class)->create()->id, ]); $response = $this->actingAs(factory(User::class)->create()) ->post( '/app/connection-request/accept', [ 'accept' => true, 'request_id' => $connection->id ] ); $response->assertLocation('/') ->assertOk(); }
然后,除了指向您的测试数据库(本地)的
phpunit.xml
环境变量之外,您不应更改任何内容,并且它应该可以在您不更改代码中的任何内容的情况下工作.Then, you should not change anything except
phpunit.xml
environment variables pointing to your testing DB (locally) and it should work without you changing anything in your code.这篇关于如何使用 PHPUnit 在 Laravel 上测试更复杂的案例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- So, for you to achieve this, you have to define the correct environment variables in
- 因此,要实现这一点,您必须在