如何在不保存到数据库的情况下在Laravel中分配belongsTo关系?

How to assign a belongsTo relation in Laravel without saving to database?

提问人:cytsunny 提问时间:11/11/2023 更新时间:11/15/2023 访问量:150

问:

我有以下 2 种型号:

<?php
namespace App\Models;

class Product extends Model
{
    //some other property of the Model

    public function productType() {
        return $this->belongsTo( 'App\Models\ProductType', 'product_type_id');
    }
    
    public function theComplicatedFunctionBeingTested() {
        //Some calculation with consideration of ProductType
        return $result;
    }
}
<?php
namespace App\Models;

class ProductType extends Model
{
    //some other property of the Model
    public function products() {
        return $this->hasMany( 'App\Models\Product', 'product_id');
    }
}

现在我想在模型中编写单元测试。如果没有必要,我不想模拟模型,所以我在单元测试中创建了这些模型的真实对象。theComplicatedFunctionBeingTested()Product

<?php
use Illumniate\Foundation\Testing\TestCase;

use App\Models\Product;
use App\Models\ProductType;

class ProductFunctionTest extends TestCase
{
    private $testProduct, $testProductType;

    private function commonDataSetUp() {
        $this->testProductType = new ProductType;
        $this->testProductType->product_type_id = 1;
        $this->testProductType->name = "publication";

        $this->testProduct = new Product;
        $this->testProduct->name = "testing product";
        //I tried to assign a relation like this, but this is not working
        $this->testProduct->productType = $this->testProductType;
    }

    public function testComplicatedProductFunction() {
        $this->commonDataSetUp();
        $result = $this->testProduct->theComplicatedFunctionBeingTested();
        $this->assertEquals( 'my_expected_result', $result);
    }
}

但是,关系设置不成功。由于未设置,Laravel 代码库的默认行为是尝试访问数据库,并且由于没有为单元测试设置数据库连接,因此会引发异常并且测试失败。$this->testProduct->productType

那么,在没有数据库的情况下,建立关系的正确方法应该是什么?

php laravel 单元测试 phpunit

评论

0赞 Adi 11/11/2023
因此,当您创建模型类的对象时,默认行为是准备好/设置数据库连接,这就是建议模拟
3赞 miken32 11/11/2023
使用 sqlite 数据库进行测试。没有数据库就无法建立关系。
0赞 matiaslauriti 11/11/2023
你为什么不使用工厂
0赞 cytsunny 11/11/2023
@matiaslauriti 因为我不想拥有数据库?虽然可以为此使用sqlite,但我觉得没有必要吗?
0赞 matiaslauriti 11/11/2023
你需要它,在 docker 容器中使用 sqlite 或你的生产数据库引擎,但你需要它。我现在没有足够的时间来解释原因,但请查看我的 SO 个人资料,您会找到有关数据库测试的帮助......
2赞 Hadi Aghandeh 11/19/2023
为什么不在测试完成后删除这些项目呢?我认为最好让数据库也参与进来。所以测试会更现实。

答:

-3赞 duckstery 11/11/2023 #1

您应该尝试此代码

$this->testProduct->setRelation("productType", $this->testProductType);

setRelation记录在 Laravel 的 API 中 这里

评论

0赞 matiaslauriti 11/11/2023
这不是解决方案,他必须在测试中使用工厂
0赞 cytsunny 11/11/2023
我已经尝试过了,但它仍然不起作用。我相信只会在调用后设置关系。setRelationsave()
0赞 Khayam Khan 11/13/2023 #2

就像在评论中一样,我仍然认为你应该使用工厂,但如果你不想使用工厂,那么我认为这可能会对你有所帮助。 您可以通过 make 命令创建模型的实例,而无需将它们保存到数据库,如下所示:

$this->testProductType = ProductType::make([
   // product type model columns
]);

$this->testProduct = Product::make([
    // product model Columns
]);

,然后将 ProductType 与 Product 关联。

$this->testProduct->productType()->associate($this->testProductType);

我希望这可以解决您的问题。

评论

0赞 cytsunny 11/13/2023
我已经尝试了该函数,但如果不调用它就无法工作setRelation()save()
0赞 Khayam Khan 11/13/2023
好的,然后尝试associate()
0赞 cytsunny 11/13/2023
我也试过了,但如果不打电话就不起作用save()
0赞 Khayam Khan 11/13/2023
那么我很抱歉,我不知道任何其他解决方案可能是其他人可能会回答更好的解决方案。
0赞 Win 11/15/2023 #3

由于关系是一对多,因此可以使用

  • save() => 添加单个 ProductType,或者
  • saveMany() => 添加多个 ProductType

在您的情况下,尝试在调用关系后使用 save()

    $this->testProductType = new ProductType;
    $this->testProductType->product_type_id = 1;
    $this->testProductType->name = "publication";

    $this->testProduct = new Product;
    $this->testProduct->name = "testing product";
    // You do need to save this first to gain product_id
    $this->testProduct->save(); 
    $this->testProduct->productType()->save($this->testProductType);

如果要添加 2 个或更多 ProductType,请尝试这样做

    $this->testProduct->productType()->saveMany([
       new ProductType(['name' => 'Abc']),
       new ProductType(['name' => 'Def']),
       new ProductType(['name' => 'Ghi']),
    ]);

评论

0赞 cytsunny 11/15/2023
需要一个数据库来调用,但我的情况没有数据库。save()
0赞 Win 11/28/2023
你有什么理由想在没有数据库的情况下测试你的代码吗?您想通过此测试实现什么?你能解释一下吗?
0赞 William Martins 11/15/2023 #4

代码波纹管工作正常。因此,您可以使用 setRelation

<?php

namespace Tests\Unit;

use Illuminate\Database\Eloquent\Model;
use PHPUnit\Framework\TestCase;

class ProductType extends Model
{
    public function products()
    {
        return $this->hasMany(Product::class, 'product_id');
    }
}

class Product extends Model
{
    public function productType()
    {
        return $this->belongsTo(ProductType::class, 'product_type_id');
    }

    public function theComplicatedFunctionBeingTested()
    {
        return $this->productType->name . " " . $this->name;
    }
}

class Test extends TestCase
{
    private $testProduct, $testProductType;

    private function commonDataSetUp()
    {
        $this->testProductType = new ProductType;
        $this->testProductType->product_type_id = 1;
        $this->testProductType->name = "publication";
        $this->testProduct = new Product;
        $this->testProduct->name = "testing product";
        $this->testProduct->setRelation('productType', $this->testProductType);
    }

    public function testComplicatedProductFunction()
    {
        $this->commonDataSetUp();
        $result = $this->testProduct->theComplicatedFunctionBeingTested();
        $this->assertEquals("publication testing product", $result);
    }
}

评论

0赞 cytsunny 11/15/2023
我已经尝试了该函数,但如果不调用它就无法工作setRelation()save()
0赞 William Martins 11/16/2023
我不明白。由于我提供的代码工作正常并且测试通过。您是否看到任何错误,或者您能否提供更多代码/上下文?
0赞 cytsunny 11/16/2023
我测试该方法的实际代码是不同的(因为它是我无法在此处公开粘贴的工作代码),但基本上代码在调用时仍在尝试在数据库中搜索,从而导致连接错误,因为没有用于测试的数据库设置。这意味着 setRelation 不是工作属性,因此模型需要在数据库中搜索产品所具有的 productType。$this->productType->name
0赞 William Martins 11/17/2023
您确定错误发生在这段代码上吗?我在操场上运行了它,它在没有数据库的情况下工作:laravelplayground.com/#/snippets/......
0赞 cytsunny 11/17/2023
是的,我敢肯定正是那条线导致了问题。我紧随其后添加了该行,它尝试访问数据库。我想可能的区别要么是因为我使用的是 Laravel 9,要么是在路由函数而不是 UnitTest 中调用了 playground 代码?dd($this->testProduct->productType$this->commonDataSetUp()