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);
}
}