Unit Testing

Unit Testing

Unit and integration tests are first-class citizens with the PHP SDK. Using the DI container, mocks, stubs, and the provided \Dapr\Mocks\TestClient allows you to have very fine-grained tests.

Testing Actors

With actors, there are two things we’re interested in while the actor is under test:

  1. The returned result based on an initial state
  2. The resulting state based on the initial state

Here’s an example test a very simple actor that updates its state and returns a specific value:

  1. <?php
  2. // TestState.php
  3. class TestState extends \Dapr\Actors\ActorState
  4. {
  5. public int $number;
  6. }
  7. // TestActor.php
  8. #[\Dapr\Actors\Attributes\DaprType('TestActor')]
  9. class TestActor extends \Dapr\Actors\Actor
  10. {
  11. public function __construct(string $id, private TestState $state)
  12. {
  13. parent::__construct($id);
  14. }
  15. public function oddIncrement(): bool
  16. {
  17. if ($this->state->number % 2 === 0) {
  18. return false;
  19. }
  20. $this->state->number += 1;
  21. return true;
  22. }
  23. }
  24. // TheTest.php
  25. class TheTest extends \PHPUnit\Framework\TestCase
  26. {
  27. private \DI\Container $container;
  28. public function setUp(): void
  29. {
  30. parent::setUp();
  31. // create a default app and extract the DI container from it
  32. $app = \Dapr\App::create(
  33. configure: fn(\DI\ContainerBuilder $builder) => $builder->addDefinitions(
  34. ['dapr.actors' => [TestActor::class]],
  35. [\Dapr\DaprClient::class => \DI\autowire(\Dapr\Mocks\TestClient::class)]
  36. ));
  37. $app->run(fn(\DI\Container $container) => $this->container = $container);
  38. }
  39. public function testIncrementsWhenOdd()
  40. {
  41. $id = uniqid();
  42. $runtime = $this->container->get(\Dapr\Actors\ActorRuntime::class);
  43. $client = $this->getClient();
  44. // return the current state from http://localhost:1313/reference/api/actors_api/
  45. $client->register_get("/actors/TestActor/$id/state/number", code: 200, data: 3);
  46. // ensure it increments from http://localhost:1313/reference/api/actors_api/
  47. $client->register_post(
  48. "/actors/TestActor/$id/state",
  49. code: 204,
  50. response_data: null,
  51. expected_request: [
  52. [
  53. 'operation' => 'upsert',
  54. 'request' => [
  55. 'key' => 'number',
  56. 'value' => 4,
  57. ],
  58. ],
  59. ]
  60. );
  61. $result = $runtime->resolve_actor(
  62. 'TestActor',
  63. $id,
  64. fn($actor) => $runtime->do_method($actor, 'oddIncrement', null)
  65. );
  66. $this->assertTrue($result);
  67. }
  68. private function getClient(): \Dapr\Mocks\TestClient
  69. {
  70. return $this->container->get(\Dapr\DaprClient::class);
  71. }
  72. }
  1. <?php
  2. // TestState.php
  3. class TestState extends \Dapr\Actors\ActorState
  4. {
  5. public int $number;
  6. }
  7. // TestActor.php
  8. #[\Dapr\Actors\Attributes\DaprType('TestActor')]
  9. class TestActor extends \Dapr\Actors\Actor
  10. {
  11. public function __construct(string $id, private TestState $state)
  12. {
  13. parent::__construct($id);
  14. }
  15. public function oddIncrement(): bool
  16. {
  17. if ($this->state->number % 2 === 0) {
  18. return false;
  19. }
  20. $this->state->number += 1;
  21. return true;
  22. }
  23. }
  24. // TheTest.php
  25. class TheTest extends \PHPUnit\Framework\TestCase
  26. {
  27. public function testNotIncrementsWhenEven() {
  28. $container = new \DI\Container();
  29. $state = new TestState($container, $container);
  30. $state->number = 4;
  31. $id = uniqid();
  32. $actor = new TestActor($id, $state);
  33. $this->assertFalse($actor->oddIncrement());
  34. $this->assertSame(4, $state->number);
  35. }
  36. }

Testing Transactions

When building on transactions, you’ll likely want to test how a failed transaction is handled. In order to do that, you need to inject failures and ensure the transaction matches what you expect.

  1. <?php
  2. // MyState.php
  3. #[\Dapr\State\Attributes\StateStore('statestore', \Dapr\consistency\EventualFirstWrite::class)]
  4. class MyState extends \Dapr\State\TransactionalState {
  5. public string $value = '';
  6. }
  7. // SomeService.php
  8. class SomeService {
  9. public function __construct(private MyState $state) {}
  10. public function doWork() {
  11. $this->state->begin();
  12. $this->state->value = "hello world";
  13. $this->state->commit();
  14. }
  15. }
  16. // TheTest.php
  17. class TheTest extends \PHPUnit\Framework\TestCase {
  18. private \DI\Container $container;
  19. public function setUp(): void
  20. {
  21. parent::setUp();
  22. $app = \Dapr\App::create(configure: fn(\DI\ContainerBuilder $builder)
  23. => $builder->addDefinitions([\Dapr\DaprClient::class => \DI\autowire(\Dapr\Mocks\TestClient::class)]));
  24. $this->container = $app->run(fn(\DI\Container $container) => $container);
  25. }
  26. private function getClient(): \Dapr\Mocks\TestClient {
  27. return $this->container->get(\Dapr\DaprClient::class);
  28. }
  29. public function testTransactionFailure() {
  30. $client = $this->getClient();
  31. // create a response from https://docs.dapr.io/reference/api/state_api/
  32. $client->register_post('/state/statestore/bulk', code: 200, response_data: [
  33. [
  34. 'key' => 'value',
  35. // no previous value
  36. ],
  37. ], expected_request: [
  38. 'keys' => ['value'],
  39. 'parallelism' => 10
  40. ]);
  41. $client->register_post('/state/statestore/transaction',
  42. code: 200,
  43. response_data: null,
  44. expected_request: [
  45. 'operations' => [
  46. [
  47. 'operation' => 'upsert',
  48. 'request' => [
  49. 'key' => 'value',
  50. 'value' => 'hello world'
  51. ]
  52. ]
  53. ]
  54. ]
  55. );
  56. $state = new MyState($this->container, $this->container);
  57. $service = new SomeService($state);
  58. $service->doWork();
  59. $this->assertSame('hello world', $state->value);
  60. }
  61. }
  1. <?php
  2. // MyState.php
  3. #[\Dapr\State\Attributes\StateStore('statestore', \Dapr\consistency\EventualFirstWrite::class)]
  4. class MyState extends \Dapr\State\TransactionalState {
  5. public string $value = '';
  6. }
  7. // SomeService.php
  8. class SomeService {
  9. public function __construct(private MyState $state) {}
  10. public function doWork() {
  11. $this->state->begin();
  12. $this->state->value = "hello world";
  13. $this->state->commit();
  14. }
  15. }
  16. // TheTest.php
  17. class TheTest extends \PHPUnit\Framework\TestCase {
  18. public function testTransactionFailure() {
  19. $state = $this->createStub(MyState::class);
  20. $service = new SomeService($state);
  21. $service->doWork();
  22. $this->assertSame('hello world', $state->value);
  23. }
  24. }

Last modified January 1, 0001