How to test a workflow which uses a loop to call an activity?

Recently I’ve tried to test a workflow following this official tutorial.

For PHP-SDK I can use new ActivityMocker() and expectCompletion() to mock an activity call to return expected result. And it works great.

But suppose we have a workflow with #[WorkflowMethod] as follows:

public function send(array $values)
{
    $results = [];
    foreach ($values as $value) {
          $results[] = yield $this->activity->doSomething($value);

          // here comes some other manipulation with results
     }
     return $results;
}

How can I mock an activity call to test this workflow when activity is called inside the loop?

This way is working only in case when I call the activity once:

$this->activityMocks->expectCompletion('SomeActivity.doSomething', ...);

You mean they secondary call can not being captured? This seems like a bug, if you can - please fill a ticket in PHP-SDK. Sounds like some issue with internal call counter.

Yes, for example I have the following PHPUnit test for my workflow:

public function testWorkflowSend()
{
        $workflow = $this->workflowClient->newWorkflowStub(SomeWorkflow::class);
        $run = $this->workflowClient->start($workflow, ['value1', 'value2', 'value3']);
        $this->assertSame(['someResult1', 'someResult2', 'someResult3'], $run->getResult('array'));
}

And the code of the workflow is the same as I mentioned above:

public function send(array $values)
{
    $results = [];
    foreach ($values as $value) {
          $results[] = yield $this->activity->doSomething($value);

          // here comes some other manipulation with results
     }
     return $results;
}

Now I need to mock the activity call results in the test. First I set up an ActivityMocker.

protected function setUp(): void
{
    $this->activityMocks = new ActivityMocker();
     ...
}

Then if I do it like this:

public function testWorkflowSend()
{
     $this->activityMocks->expectCompletion('SomeActivity.doSomething', ['someResult1', 'someResult2', 'someResult3'])
      ...
}

The result of running the workflow will be:

array(3) {
[0] => array(3) {
    [0] => string(11) "someResult1",
    [1] => string(11) "someResult2",
    [2] => string(11) "someResult3",
}, 
[1] => array(3) {
    [0] => string(11) "someResult1",
    [1] => string(11) "someResult2",
    [2] => string(11) "someResult3",
}, 
[2] => array(3) {
    [0] => string(11) "someResult1",
    [1] => string(11) "someResult2",
    [2] => string(11) "someResult3",
}

Instead of:

array(9) {
    [0] => string(11) "someResult1",
    [1] => string(11) "someResult2",
    [2] => string(11) "someResult3",
}

And I don’t consider it’s a bug, cause it seems that expectCompletion doesn’t have any possibility to capture several calls of the Activity with different completion results.

It will be great if we have a choice to set expectCompletion result for each call of the Activity inside the loop. For example:

expectCompletion('SomeActivity.doSomething', 'someResult1', 1); // for the first call
expectCompletion('SomeActivity.doSomething', 'someResult2', 2); // for the second
expectCompletion('SomeActivity.doSomething', 'someResult3', 3); // for the third

And the same with expectFailure():

expectCompletion('SomeActivity.doSomething', 'someResult1', 1); // for the first call
expectFailure('SomeActivity.doSomething', new Error(), 2);  // except error for the second call

I can fill a ticket for a feature request if my proposal sounds right.

Hi, yes. Feature request will be appreciated. We will def review DX of this feature after completing the interceptors.