如何在测试过程中通过Eloquent ORM模拟记录插入?

huangapple go评论80阅读模式
英文:

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([&#39;message&#39;=&gt;&#39;Insert Success&#39;,&#39;record&#39;=&gt;$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-&gt;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(),
            ]
        ]);
    }
}

正如你所看到的,我按照以下顺序进行操作:

  1. 冻结时间,这样当你使用now()today()或任何Carbon助手时,它将始终返回完全相同的时间。当你想测试它是否返回正确的时间和格式时,这很有用。
  2. 设置一个期望的常见值。
  3. 调用所需URL,使用已知数据。
  4. 断言它返回状态码201
  5. 检查是否添加了一条记录(在运行测试之前,我们知道没有记录,所以在进行HTTP调用之前不需要检查是否为空)。
  6. 检查添加的记录是否具有预期的数据(你只使用value属性,所以我只查找这个属性)。
  7. 最后,检查返回的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-&gt;freezeTime();

        $expectedValue = &#39;nice string to set in the value property&#39;;

        $response = $this-&gt;put(&#39;/record&#39;, [&#39;value&#39; =&gt; $expectedValue])
            -&gt;assertCreated();

        $this-&gt;assertDatabaseCount(&#39;records&#39;, 1);

        $this-&gt;assertDatabaseHas(
            &#39;records&#39;,
            [&#39;value&#39; =&gt; $expectedValue]
        );

        $response-&gt;assertJson([
            &#39;message&#39; =&gt; &#39;Insert Success&#39;,
            &#39;record&#39; =&gt; [
                &#39;id&#39; =&gt; 1,
                &#39;value&#39; =&gt; $expectedValue,
                &#39;created_at&#39; =&gt; now()-&gt;toDateTimeString(),
                &#39;updated_at&#39; =&gt; now()-&gt;toDateTimeString(),
            ]
        ]);
    }
}

As you can see, I am doing stuff in this order:

  1. Freezing time, so when you use now() or today() 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.
  2. Setting a common value to expect back.
  3. Doing the call to the desired URL, with known data.
  4. Asserting it returned the status 201.
  5. 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)
  6. Checking that the added record has the expected data (you only use the value property, so I am only looking for that)
  7. 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 -&gt;assertCreated() in a chain call too.

More info:

huangapple
  • 本文由 发表于 2023年7月14日 01:45:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76682030.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定