Summer Sale! All accounts are 50% off this week.

simon-davies's avatar

Mocking object not working with a constructor parameter

Hello, I'm working on an application which will retrieve some data from an external API. I'm using an class called Orders which is resolved via Laravel's Service Container to make the API requests, but I've run into an issue mocking the class in my tests.

The following works as expected.

routes/web.php

Route::get('/orders', function(\App\Orders $orders){
    return $orders->getOrders();
});

app\Orders.php

<?php

namespace App;

class Orders
{
    public function getOrders()
    {
        return [
            [
                'id' => 123
            ]
        ];
    }
}

tests/Feature/OrderTest.php

<?php

namespace Tests\Feature;

use App\Orders;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class OrderTest extends TestCase
{
    use RefreshDatabase;

    public function test_orders()
    {
        $this->mock(Orders::class, function($mock){
            $mock->shouldReceive('getOrders')->andReturn([
                [
                    'id' => 123,
                ],
                [
                    'id' => 456,
                ],
            ]);
        });

        $response = $this->get('/orders');
        $response->assertStatus(200)->assertJson([
            [
                'id' => 123,
            ],
            [
                'id' => 456,
            ],
        ]);
    }
}

But my app will have many Shops, each will have their own orders which will be retrieved from a different API base URL. So I want to be able to resolve the object from the Service Container but pass in a GuzzleHttp\Client based on the shop. So when I make the following changes the app\Orders object is no longer being mocked.

routes/web.php

Route::get('/orders', function(){
    $orders = app()->make(\App\Orders::class, ['client' => new \GuzzleHttp\Client()]);
    return $orders->getOrders();
});

app\Orders.php

<?php

namespace App;

use GuzzleHttp\Client;

class Orders
{
    private $client;

    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    public function getOrders()
    {
        return [
            [
                'id' => 123
            ]
        ];
    }
}

app\Providers\AppServiceProvider.php

<?php

namespace App\Providers;

use App\Orders;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->app->bind(Orders::class, function($app, $args){
            return new Orders($args['client']);
        });
    }
}

I can't find any examples of something similar, so I'm probably not resolving the object correctly from the Service Container, but I can't think of any way to better structure this code. Can anyone help?

0 likes
1 reply
martinbean's avatar

@simon-davies If the order API changes based on a shop, then you’re not going to be able to bind it in a service provider, because you need to know the shop before you bind shop-related services.

If this is a multi-tenant or SaaS-type application, then I’d bind the service when you actually resolve the shop you’re working with. I’d also create a generic ShopService interface and bind API-specific classes to that interface, rather than just having a generic Orders class. That class name infers nothing about what it actually is or does. What is it? A collection or orders? No, it’s a polymorphic service-type class from your example.

Please or to participate in this conversation.