使用 findOrFail 时得到 "Trying to get property 'subtotal' of non-object"

Getting "Trying to get property 'subtotal' of non-object" when using findOrFail

我在服务中得到了以下代码:

public function add(array $data) : Order
    {
        // retrieve item data
        $item       = MenuItem::findOrFail($data[OrderItem::ORDER_ITEM_ITEM_ID]);
        $quantity   = $data[OrderItem::ORDER_ITEM_QUANTITY];
        $order_no   = $data[Order::ORDER_NO] ?? null;
        $session_id = $data[Order::ORDER_SESSION_ID];

        $order = $order_no ? Order::findOrFail($order_no) : $this->createOrder([Order::ORDER_SESSION_ID => $session_id]);

        $order_item = $this->order_item->createOrderItem($order->order_no, $item, $quantity);

        // update order total
        $order->subtotal += $order_item->subtotal;
        $order->total    += $order_item->subtotal;
        $order->update();

        return $order;
    }

当 运行 我得到的测试:

Getting "Trying to get property 'subtotal' of non-object"

检索到的对象不为空,否则检索失败。但是小计和总计都不属于该对象。如果我打印对象,我会得到除这两个之外的其他字段。它们都在我的 $fillable 数组中,我在迁移中定义了一个 ->default(0);

怎么回事?

编辑

PHP单位输出如下:

PHPUnit 8.5.0 by Sebastian Bergmann and contributors.

array:5 [
  "message" => "Trying to get property 'subtotal' of non-object"
  "exception" => "ErrorException"
  "file" => "/Users/bigweld/Sites/restaurantbe/app/Services/OrderService.php"
  "line" => 110
  "trace" => array:33 [
    0 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/app/Services/OrderService.php"
      "line" => 110
      "function" => "handleError"
      "class" => "Illuminate\Foundation\Bootstrap\HandleExceptions"
      "type" => "->"
    ]
    1 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/app/Http/Controllers/API/OrderController.php"
      "line" => 57
      "function" => "add"
      "class" => "App\Services\OrderService"
      "type" => "->"
    ]
    2 => array:3 [
      "function" => "add"
      "class" => "App\Http\Controllers\API\OrderController"
      "type" => "->"
    ]
    3 => array:3 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Controller.php"
      "line" => 54
      "function" => "call_user_func_array"
    ]
    4 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php"
      "line" => 45
      "function" => "callAction"
      "class" => "Illuminate\Routing\Controller"
      "type" => "->"
    ]
    5 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Route.php"
      "line" => 219
      "function" => "dispatch"
      "class" => "Illuminate\Routing\ControllerDispatcher"
      "type" => "->"
    ]
    6 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Route.php"
      "line" => 176
      "function" => "runController"
      "class" => "Illuminate\Routing\Route"
      "type" => "->"
    ]
    7 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Router.php"
      "line" => 681
      "function" => "run"
      "class" => "Illuminate\Routing\Route"
      "type" => "->"
    ]
    8 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php"
      "line" => 130
      "function" => "Illuminate\Routing\{closure}"
      "class" => "Illuminate\Routing\Router"
      "type" => "->"
    ]
    9 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php"
      "line" => 105
      "function" => "Illuminate\Pipeline\{closure}"
      "class" => "Illuminate\Pipeline\Pipeline"
      "type" => "->"
    ]
    10 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Router.php"
      "line" => 683
      "function" => "then"
      "class" => "Illuminate\Pipeline\Pipeline"
      "type" => "->"
    ]
    11 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Router.php"
      "line" => 658
      "function" => "runRouteWithinStack"
      "class" => "Illuminate\Routing\Router"
      "type" => "->"
    ]
    12 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Router.php"
      "line" => 624
      "function" => "runRoute"
      "class" => "Illuminate\Routing\Router"
      "type" => "->"
    ]
    13 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Routing/Router.php"
      "line" => 613
      "function" => "dispatchToRoute"
      "class" => "Illuminate\Routing\Router"
      "type" => "->"
    ]
    14 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php"
      "line" => 177
      "function" => "dispatch"
      "class" => "Illuminate\Routing\Router"
      "type" => "->"
    ]
    15 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php"
      "line" => 130
      "function" => "Illuminate\Foundation\Http\{closure}"
      "class" => "Illuminate\Foundation\Http\Kernel"
      "type" => "->"
    ]
    16 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php"
      "line" => 105
      "function" => "Illuminate\Pipeline\{closure}"
      "class" => "Illuminate\Pipeline\Pipeline"
      "type" => "->"
    ]
    17 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php"
      "line" => 152
      "function" => "then"
      "class" => "Illuminate\Pipeline\Pipeline"
      "type" => "->"
    ]
    18 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php"
      "line" => 117
      "function" => "sendRequestThroughRouter"
      "class" => "Illuminate\Foundation\Http\Kernel"
      "type" => "->"
    ]
    19 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php"
      "line" => 434
      "function" => "handle"
      "class" => "Illuminate\Foundation\Http\Kernel"
      "type" => "->"
    ]
    20 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php"
      "line" => 406
      "function" => "call"
      "class" => "Illuminate\Foundation\Testing\TestCase"
      "type" => "->"
    ]
    21 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/tests/Feature/OrdersTest.php"
      "line" => 428
      "function" => "json"
      "class" => "Illuminate\Foundation\Testing\TestCase"
      "type" => "->"
    ]
    22 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestCase.php"
      "line" => 1408
      "function" => "when_adding_the_same_item_twice_to_an_empty_order_then_only_one_order_item_is_created_but_its_quantity_increases"
      "class" => "Tests\Feature\OrdersTest"
      "type" => "->"
    ]
    23 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestCase.php"
      "line" => 1028
      "function" => "runTest"
      "class" => "PHPUnit\Framework\TestCase"
      "type" => "->"
    ]
    24 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestResult.php"
      "line" => 691
      "function" => "runBare"
      "class" => "PHPUnit\Framework\TestCase"
      "type" => "->"
    ]
    25 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestCase.php"
      "line" => 756
      "function" => "run"
      "class" => "PHPUnit\Framework\TestResult"
      "type" => "->"
    ]
    26 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestSuite.php"
      "line" => 597
      "function" => "run"
      "class" => "PHPUnit\Framework\TestCase"
      "type" => "->"
    ]
    27 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestSuite.php"
      "line" => 597
      "function" => "run"
      "class" => "PHPUnit\Framework\TestSuite"
      "type" => "->"
    ]
    28 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/Framework/TestSuite.php"
      "line" => 597
      "function" => "run"
      "class" => "PHPUnit\Framework\TestSuite"
      "type" => "->"
    ]
    29 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/TextUI/TestRunner.php"
      "line" => 621
      "function" => "run"
      "class" => "PHPUnit\Framework\TestSuite"
      "type" => "->"
    ]
    30 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/TextUI/Command.php"
      "line" => 200
      "function" => "doRun"
      "class" => "PHPUnit\TextUI\TestRunner"
      "type" => "->"
    ]
    31 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/src/TextUI/Command.php"
      "line" => 159
      "function" => "run"
      "class" => "PHPUnit\TextUI\Command"
      "type" => "->"
    ]
    32 => array:5 [
      "file" => "/Users/bigweld/Sites/restaurantbe/vendor/phpunit/phpunit/phpunit"
      "line" => 61
      "function" => "main"
      "class" => "PHPUnit\TextUI\Command"
      "type" => "::"
    ]
  ]
]

根据我们在评论中的对话,您的函数中存在的问题之一,并因此导致您的测试出现问题,是无法保证每次结果的依赖性。也就是说,您在 add 函数中对其他服务的调用无法保证 提供特定输出。

这样想你的测试。

  • 我从零件商品和零小计的订单开始
  • 我想在该订单中添加一个项目
  • 我想以一件商品和 10 美元小计的订单结束

在此测试中,您不关心如何创建订单项,您只关心它创建的,与一个特定的值,以便在将其添加到您的订单时,最终结果就是您所期望的。

在这种情况下,您希望 $this->order_item 成为一个模拟对象,然后您可以将对 createOrderItem 的调用模拟为 return 一个已知的订单对象。向您展示一个完整的解决方案有点困难,因为它可能涉及一些架构更改,但您的测试将类似于:

public function it_adds_an_item_to_an_order()
{
  $orderItem = Mock(OrderItem::class); // Use whatever mocking lib is available
  $orderitem->shouldReceive('createOrderItem').andReturn(new OrderItem([$subtotal=>10.00]));
  $orderService = new OrderService($orderItem);

  $order = $orderService->add($data);

  $this->assertNotNull($order);
  $this->assertEquals(10.00, $order->subtotal);
}

希望这对您有所帮助。