Laravel Eloquent 模型单元测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29715792/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 11:22:52  来源:igfitidea点击:

Laravel Eloquent Model Unit testing

laravellaravel-4phpuniteloquent

提问by henrik

I am trying to write a testcase which tests the association and detachment of the relationship between two Eloquent models in Laravel 4.2

我正在尝试编写一个测试用例来测试 Laravel 4.2 中两个 Eloquent 模型之间关系的关联和分离

Here's my test case:

这是我的测试用例:

class BookingStatusSchemaTest extends TestCase
{

  private $statusText = "Confirmed";
  private $bookingStub;
  private $statusStub;

  public function testMigrateService()
  {

    $this->createTestData();

    $booking = $this->bookingStub;
    $status = $this->statusStub;

    /**
     * Check that the booking has no status. OK
     */
    $this->assertNull($booking->status);

    /**
     * Check that status has no booking. OK
     */
    $this->assertEquals(count($status->bookings), 0);

    /**
     * Add a status to the booking. OK
     */
    $booking->status()->associate($this->statusStub);

    /**
     * Check that status has a booking. NOT OK - This gives error
     */
    $this->assertEquals(count($status->bookings), 1);

    /**
     * Check that the booking has a status. OK
     */
    $this->assertNotNull($booking->status);

    /**
     * Do NOT delete the status, just set the reference
     * to it to null.
     */
    $booking->status = null;

    /**
     * And check again. OK
     */
    $this->assertNull($booking->status);
  }

  private function createTestData()
  {

    $bookingStatus = BookingStatus::create([ 
        'status' => $this->statusText 
    ]);

    $booking = Booking::create([ ]);

    $this->bookingStub = $booking;
    $this->statusStub = $bookingStatus;

  }

}

When I execute it I get:

当我执行它时,我得到:

There was 1 failure:

1) BookingStatusSchemaTest::testMigrateService
Failed asserting that 1 matches expected 0.

Booking model:

预订模式:

class Booking extends Eloquent {

  /**
  * A booking have a status
  */
  public function status()
  {
    return $this->belongsTo('BookingStatus');
  }

}

BookingStatus Model:

预订状态模型:

class BookingStatus extends Eloquent
{
  protected $table = 'booking_statuses';
  protected $guarded = [ 'id' ];
  protected $fillable = ['status'];

  /**
   * A booking status belongs to a booking
   */
  public function bookings()
  {
    return $this->hasMany('Booking');
  }

}

Here's the migration Schema for bookingstatus:

这是bookingstatus的迁移架构:

  Schema::create('booking_statuses', function(Blueprint $table)
  {
    $table->increments('id');
    $table->string('status');
    $table->timestamps();
  });

And heres for booking:

以及这里的预订:

Schema::create('bookings', function(Blueprint $table)
{
  $table->increments('id');
  $table->unsignedInteger('booking_status_id')->nullable();
  $table->timestamps();
});

What do I have to add / change to be able to verify the relationship in my test case?

我必须添加/更改什么才能验证测试用例中的关系?

回答by Quasdunk

It's been a while and I had totally forgotten about this question. Since OP still sems interested in it, I'll try to answer the question in some way.

已经有一段时间了,我完全忘记了这个问题。由于 OP 仍然对它感兴趣,我将尝试以某种方式回答这个问题。

So I assume the actual task is: How to test the correct relationship between two Eloquent models?

所以我假设实际的任务是:如何测试两个 Eloquent 模型之间的正确关系?

I think it was Adam Wathan who first suggested abandoning terms like "Unit Tests" and "Functional Tests" and "I-have-no-idea-what-this-means Tests" and just separate tests into two concerns/concepts: Features and Units, where Features simply describe features of the app, like "A logged in user can book a flight ticket", and Units describe the lower level Units of it and the functionality they expose, like "A booking has a status".

我认为是 Adam Wathan 首先建议放弃诸如“单元测试”和“功能测试”以及“我不知道这是什么意思的测试”之类的术语,而将测试分为两个关注点/概念:特性和Units,其中 Features 简单地描述了应用程序的功能,比如“登录的用户可以预订机票”,而 Units 描述它的较低级别的 Units 和它们公开的功能,比如“预订有一个状态”。

I like this approach a lot, and with that in mind, I'd like to refactor your test:

我非常喜欢这种方法,考虑到这一点,我想重构您的测试:

class BookingStatusSchemaTest extends TestCase
{
    /** @test */
    public function a_booking_has_a_status()
    {
        // Create the world: there is a booking with an associated status
        $bookingStatus = BookingStatus::create(['status' => 'confirmed']);
        $booking = Booking::create(['booking_status_id' => $bookingStatus->id]);

        // Act: get the status of a booking
        $actualStatus = $booking->status;

        // Assert: Is the status I got the one I expected to get?
        $this->assertEquals($actualStatus->id, $bookingStatus->id);
    }


    /** @test */    
    public function the_status_of_a_booking_can_be_revoked()
    {
        // Create the world: there is a booking with an associated status
        $bookingStatus = BookingStatus::create(['status' => 'confirmed']);
        $booking = Booking::create(['booking_status_id' => $bookingStatus->id]);

        // Act: Revoke the status of a booking, e.g. set it to null
        $booking->revokeStatus();

        // Assert: The Status should be null now
        $this->assertNull($booking->status);
    }
}

This code is not tested!

此代码未经测试!

Note how the function names read like a description of a Booking and its functionality. You don't really care about the implementation, you don't have to know where or how the Booking gets its BookingStatus - you just want to make sure that if there is Booking with a BookingStatus, you can get that BookingStatus. Or revoke it. Or maybe change it. Or do whatever. Your test shows how you'd like to interact with this Unit. So write the test and then try to make it pass.

请注意函数名称如何读起来像 Booking 及其功能的描述。您并不真正关心实现,您不必知道 Booking 在何处或如何获取其 BookingStatus - 您只想确保如果有 BookingStatus 的 Booking,您可以获得该 BookingStatus。或者撤销它。或者也许改变它。或者做任何事情。您的测试显示了您希望如何与该单元进行交互。因此,编写测试,然后尝试使其通过。

The main flaw in your test is probably that you're kind of "afraid" of some magic to happen. Instead, think of your models as Plain Old PHP Objects - because that's what they are! And you wouldn't run a test like this on a POPO:

您测试中的主要缺陷可能是您有点“害怕”某些魔法的发生。相反,将您的模型视为普通的旧 PHP 对象——因为它们就是这样!你不会在 POPO 上运行这样的测试:

/**
 * Do NOT delete the status, just set the reference
 * to it to null.
 */
$booking->status = null;

/**
 * And check again. OK
 */
$this->assertNull($booking->status);

It's a really broad topic and every statement about it inevitably opinioted. There are some guidelines that help you get along, like "only test your own code", but it's really hard to put all the peaces together. Luckily, the aforementioned Adam Wathan has a really excellent video course named "Test Driven Laravel" where he test-drives a whole real-world Laravel application. It may be a bit costly, but it's worth every penny and helps you understand testing way more than some random dude on StackOverflow :)

这是一个非常广泛的话题,每一个关于它的声明都不可避免地受到了质疑。有一些指导方针可以帮助你相处,比如“只测试你自己的代码”,但真的很难把所有的和平放在一起。幸运的是,前面提到的 Adam Wathan 有一个非常出色的视频课程,名为“ Test Driven Laravel”,他在其中试驾了整个真实世界的 Laravel 应用程序。它可能有点贵,但每一分钱都是值得的,并且比 StackOverflow 上的一些随机家伙更能帮助您了解测试方式:)

回答by IGP

To test you're setting the correct Eloquent relationship, you have to run assertions against the relationship class ($model->relation()). You can assert

要测试您是否设置了正确的 Eloquent 关系,您必须对关系类 ( $model->relation())运行断言。你可以断言

  • It's the correct relationship type by asserting $model->relation()is an instance of HasMany, BelongsTo, HasManyThrough... etc
  • It's relating to the correct model by using $model->relation()->getRelated()
  • It's using the correct foreign key by using $model->relation()->getForeignKey()
  • The foreign key exists as a column in the table by using Schema::getColumListing($table)(Here, $tableis either $model->relation()->getRelated()->getTable()if it's a HasManyrelationship or $model->relation()->getParent()->getTable()if it's a BelongsTorelationship)
  • 通过断言$model->relation()HasMany, BelongsTo, HasManyThrough... 等的实例,这是正确的关系类型
  • 它通过使用与正确的模型有关 $model->relation()->getRelated()
  • 它通过使用正确的外键 $model->relation()->getForeignKey()
  • 外键存在,如通过使用表中的列Schema::getColumListing($table)(在这里,$table或者是$model->relation()->getRelated()->getTable()如果它是一个HasMany关系或者$model->relation()->getParent()->getTable()如果它是一个BelongsTo关系)

For example. Let's say you've got a Parentand a Childmodel where a Parenthas many Childthrough the children()method using parent_idas foreign key. Parentmaps the parentstable and Childmaps the childrentable.

例如。假设您有 aParent和一个Child模型,其中 aParent有很多Child通过children()使用parent_id作为外键的方法。Parent映射parents表并Child映射children表。

$parent = new Parent;
# App\Parent
$parent->children()
# Illuminate\Database\Eloquent\Relations\HasMany
$parent->children()->getRelated()
# App\Child
$parent->children()->getForeignKey()
# 'parent_id'
$parent->children()->getRelated()->getTable()
# 'children'
Schema::getColumnListing($parent->children()->getRelated()->getTable())
# ['id', 'parent_id', 'col1', 'col2', ...]

EDITAlso, this does not touch the database since we're never saving anything. However, the database needs to be migrated or the models will not be associated with any tables.

编辑此外,这不会触及数据库,因为我们从不保存任何内容。但是,数据库需要迁移,否则模型将不会与任何表相关联。