英文:
How I can Mock Record Insertion via Eloquent ORM during testing?
问题
我创建了以下控制器:
```php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use App\Models\Record;
class SpyController extends BaseController
{
public function add(Request $request)
{
$model = new Record();
$model->value = $request->get('value');
return new JsonResponse(['message' => 'Insert Success', 'record' => $model], 201);
}
}
而且我想要测试在成功插入时,将会插入一个值:
namespace Tests\Controller;
use Illuminate\Testing\Fluent\AssertableJson;
use Laravel\Sanctum\Sanctum;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\DatabaseTestCase;
class SpyRoutesTest extends TestCase
{
public function addSuccess()
{
DB::fake();
$response = $this->put('/record', ['value' => 'lalalala']);
$this->assertStatus(201);
}
}
但在我的情况下,我如何模拟记录的实际插入?fake() 只会假定插入成功吗?此外,我如何模拟数据库中的错误,例如连接超时?
<details>
<summary>英文:</summary>
I made the following controller:
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use App\Models\Record;
class SpyController extends BaseController
{
public function add(Request $request)
{
$model = new Record();
$model->value = $request->get('value');
return new JsonResponse(['message'=>'Insert Success','record'=>$model],201);
}
}
And I want to test that upon a successful insertion, a value will be inserted:
namespace Tests\Controller;
use Illuminate\Testing\Fluent\AssertableJson;
use Laravel\Sanctum\Sanctum;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\DatabaseTestCase;
class SpyRoutesTest extends TestCase
{
public function addSuccess()
{
DB::fake()
$response = $this->put('/record',['value'=>'lalalala']);
$this->assertStatus(201);
}
}
But in my case, how I can mock the actual Insertion of the record? Will the fake() just assume a successfull insertion? Also, how I can mock an error in the database for example a connection timeout as well?
</details>
# 答案1
**得分**: 2
你的解决方案很简单:完全不要模拟数据库。我以前已经回答过你的确切问题(我在我的个人资料中有更多与测试相关的答案)。
你必须实际测试插入操作,所以需要一个虚假的数据库(在同一个连接中或不同的连接,但仅用于测试),然后插入数据,但使用`RefreshDatabase`以便数据被删除。
你的测试应该像这样:
```php
use Illuminate\Foundation\Testing\RefreshDatabase;
class SpyRoutesTest extends TestCase
{
use RefreshDatabase;
public function test_record_should_be_added_with_expected_value()
{
$this->freezeTime();
$expectedValue = '要设置的值属性的好字符串';
$response = $this->put('/record', ['value' => $expectedValue])
->assertCreated();
$this->assertDatabaseCount('records', 1);
$this->assertDatabaseHas(
'records',
['value' => $expectedValue]
);
$response->assertJson([
'message' => '插入成功',
'record' => [
'id' => 1,
'value' => $expectedValue,
'created_at' => now()->toDateTimeString(),
'updated_at' => now()->toDateTimeString(),
]
]);
}
}
正如你所看到的,我按照以下顺序进行操作:
- 冻结时间,这样当你使用
now()
或today()
或任何Carbon助手时,它将始终返回完全相同的时间。当你想测试它是否返回正确的时间和格式时,这很有用。 - 设置一个期望的常见值。
- 调用所需URL,使用已知数据。
- 断言它返回状态码
201
。 - 检查是否添加了一条记录(在运行测试之前,我们知道没有记录,所以在进行HTTP调用之前不需要检查是否为空)。
- 检查添加的记录是否具有预期的数据(你只使用
value
属性,所以我只查找这个属性)。 - 最后,检查返回的JSON是否具有预期的格式和完全相同的预期数据。如果你有其他列,或者没有返回其他内容,例如时间(在这种情况下,你可以删除冻结),则可能需要在测试中修改返回的内容。
- 我在最后进行了这个检查,因为我之前在测试其他东西,但我删除了它(没有将它添加到这个示例中),所以我把它留在最后,你完全可以在
->assertCreated()
后的链式调用中将这个检查移动到其他位置。
- 我在最后进行了这个检查,因为我之前在测试其他东西,但我删除了它(没有将它添加到这个示例中),所以我把它留在最后,你完全可以在
更多信息:
英文:
Your solution is easy: you do not mock the database AT ALL. I have replied your exact question before (I have more answers related to testing in my profile).
You have to literally test the insertion, so have a fake database (another database name on the same connection (or not) but only used for testing) and insert the data, but use RefreshDatabase
so it gets deleted.
Your test should be like this:
use Illuminate\Foundation\Testing\RefreshDatabase;
class SpyRoutesTest extends TestCase
{
use RefreshDatabase;
public function test_record_should_be_added_with_expected_value()
{
$this->freezeTime();
$expectedValue = 'nice string to set in the value property';
$response = $this->put('/record', ['value' => $expectedValue])
->assertCreated();
$this->assertDatabaseCount('records', 1);
$this->assertDatabaseHas(
'records',
['value' => $expectedValue]
);
$response->assertJson([
'message' => 'Insert Success',
'record' => [
'id' => 1,
'value' => $expectedValue,
'created_at' => now()->toDateTimeString(),
'updated_at' => now()->toDateTimeString(),
]
]);
}
}
As you can see, I am doing stuff in this order:
- Freezing time, so when you use
now()
ortoday()
or any carbon helper, it will always return the same exact time. This is useful when you want to test if it returns the right time and format. - Setting a common value to expect back.
- Doing the call to the desired URL, with known data.
- Asserting it returned the status
201
. - Checking that a single record was added (before running the test, we knew that we had 0, so no need to check if it was empty before doing the HTTP call)
- Checking that the added record has the expected data (you only use the
value
property, so I am only looking for that) - Finally, check that the returned JSON has the expected format and exactly the expected data back. Here you may need to modify the returning stuff in the test if you have other columns, or you are not returning, for example, the time (in that case you can remove the freeze).
- I did do this last check at the end, because I was testing something else before, but I removed it (not added it to this example) so I left it at the end, you can definitely move this check after the
->assertCreated()
in a chain call too.
- I did do this last check at the end, because I was testing something else before, but I removed it (not added it to this example) so I left it at the end, you can definitely move this check after the
More info:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论