Home > Mobile >  How to mock a class that depends on class from vendor (AwsClient)
How to mock a class that depends on class from vendor (AwsClient)

Time:05-20

Hi I am trying to write unit tests for a specific case where I have to test a class that is depending on Aws\DynamoDb\DynamoDbClient that comes from aws-sdk-php package.

Here is how my class look like:

use Aws\DynamoDb\DynamoDbClient;
use Aws\Result;

class DynamoDbService implements DynamoDbServiceInterface
{
    protected DynamoDbClient $client; // Class from aws-sdk-php 

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

    public function describe(string $tableName): Result
    {
        $result = $this->client->describeTable([
            'TableName' => $tableName
        ]);

        return $result;
    }
}

I came up with a solution but it does not actually work:

class DynamoDbServiceTest extends TestCase
{
    /**
     * @test
     */
    public function testDescribe()
    {
        $client = $this->createMock(DynamoDbClient::class);
        $resultMock = $this->createMock(Result::class);

        $client
            ->method('describeTable')
            ->will($this->returnValue($resultMock));

        $dynamoDbService = new DynamoDbService($client);

        $result = $dynamoDbService->describe('table_example_name');
        $this->assertInstanceOf($result, Result::class);
    }
}

My solution gave me an error:

There was 1 error:

1) Tests\Unit\DynamoDbServiceTest::testDescribe
PHPUnit\Framework\MockObject\MethodCannotBeConfiguredException: Trying to configure method "describeTable" which cannot be configured because it does not exist, has not been specified, is final, or is static

/home/project/src/tests/unit/DynamoDbServiceTest.php:21

In the end the problem is mocking an AwsClient class. It does not have a describeTable method, but it is rather called by using magic method __call. How can I get a working solution and mock such a class?

CodePudding user response:

I'd move the DynamoDbService in a separate interface, and inject the interface instead of the aws object.

I had a similar approach with Stripe SDK,

The main advantage is to avoid fixing your code if amazon updates its SDK and have better controls about which features of the SDK you're using

interface StripeGatewayInterface
{
    public function createCustomer(Customer $customer): string;
}


class Stripe implements StripeGatewayInterface
{
    public function __construct(
        private readonly StripeClient $stripe
    ) {
    }

    // your methods with the SDK
}

CodePudding user response:

Okey I believe I found an anwser. If you came here with the same problem you need to trigger mock builder first with using addMethods and later on use this mock for setting behaviour of mock methods.

class DynamoDbServiceTest extends TestCase
{
    /**
     * @test
     */
    public function testDescribe()
    {
        $resultMock = $this->createMock(Result::class);

        $client = $this->getMockBuilder(DynamoDbClient::class)
            ->disableOriginalConstructor()
            ->addMethods(['describeTable'])
            ->getMock();

        $client->method('describeTable')->will($this->returnValue($resultMock));

        $dynamoDbService = new DynamoDbService($client);

        $result = $dynamoDbService->describe('table_example_name');
        $this->assertInstanceOf(Result::class, $result);
    }
}
  • Related