title | description | services | author | manager | keywords | ms.service | ms.devlang | ms.topic | ms.date | ms.author |
---|---|---|---|---|---|---|---|---|---|---|
Azure Durable Functions unit testing |
Learn how to unit test Durable Functions. |
functions |
kadimitr |
jeconnoc |
azure-functions |
multiple |
conceptual |
02/28/2018 |
kadimitr |
Unit testing is an important part of modern software development practices. Unit tests verify business logic behavior and protect from introducing unnoticed breaking changes in the future. Durable Functions can easily grow in complexity so introducing unit tests will help to avoid breaking changes. The following sections explain how to unit test the three function types - Orchestration client, Orchestrator, and Activity functions.
The examples in this article require knowledge of the following concepts and frameworks:
Mocking is supported via two abstract classes in Durable Functions:
These classes are base classes for DurableOrchestrationClient and DurableOrchestrationContext that define Orchestration Client and Orchestrator methods. The mocks will set expected behavior for base class methods so the unit test can verify the business logic. There is a two-step workflow for unit testing the business logic in the Orchestration Client and Orchestrator:
- Use the base classes instead of the concrete implementation when defining Orchestration Client and Orchestrator's signatures.
- In the unit tests mock the behavior of the base classes and verify the business logic.
Find more details in the following paragraphs for testing functions that use the orchestration client binding and the orchestrator trigger binding.
In this section, the unit test will validate the logic of the following HTTP trigger function for starting new orchestrations.
[!code-csharpMain]
The unit test task will be to verify the value of the Retry-After
header provided in the response payload. So the unit test will mock some of DurableOrchestrationClientBase methods to ensure predictable behavior.
First, a mock of the base class is required, DurableOrchestrationClientBase. The mock can be a new class that implements DurableOrchestrationClientBase. However, using a mocking framework like moq simplifies the process:
// Mock DurableOrchestrationClientBase
var durableOrchestrationClientBaseMock = new Mock<DurableOrchestrationClientBase>();
Then StartNewAsync
method is mocked to return a well-known instance ID.
// Mock StartNewAsync method
durableOrchestrationClientBaseMock.
Setup(x => x.StartNewAsync(functionName, It.IsAny<object>())).
ReturnsAsync(instanceId);
Next CreateCheckStatusResponse
is mocked to always return an empty HTTP 200 response.
// Mock CreateCheckStatusResponse method
durableOrchestrationClientBaseMock
.Setup(x => x.CreateCheckStatusResponse(It.IsAny<HttpRequestMessage>(), instanceId))
.Returns(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(string.Empty),
});
TraceWriter
is also mocked:
// Mock TraceWriter
var traceWriterMock = new Mock<TraceWriter>(TraceLevel.Info);
Now the Run
method is called from the unit test:
// Call Orchestration trigger function
var result = await HttpStart.Run(
new HttpRequestMessage()
{
Content = new StringContent("{}", Encoding.UTF8, "application/json"),
RequestUri = new Uri("http://localhost:7071/orchestrators/E1_HelloSequence"),
},
durableOrchestrationClientBaseMock.Object,
functionName,
traceWriterMock.Object);
The last step is to compare the output with the expected value:
// Validate that output is not null
Assert.NotNull(result.Headers.RetryAfter);
// Validate output's Retry-After header value
Assert.Equal(TimeSpan.FromSeconds(10), result.Headers.RetryAfter.Delta);
After combining all steps, the unit test will have the following code:
[!code-csharpMain]
Orchestrator functions are even more interesting for unit testing since they usually have a lot more business logic.
In this section the unit tests will validate the output of the E1_HelloSequence
Orchestrator function:
[!code-csharpMain]
The unit test code will start with creating a mock:
var durableOrchestrationContextMock = new Mock<DurableOrchestrationContextBase>();
Then the activity method calls will be mocked:
durableOrchestrationContextMock.Setup(x => x.CallActivityAsync<string>("E1_SayHello", "Tokyo")).ReturnsAsync("Hello Tokyo!");
durableOrchestrationContextMock.Setup(x => x.CallActivityAsync<string>("E1_SayHello", "Seattle")).ReturnsAsync("Hello Seattle!");
durableOrchestrationContextMock.Setup(x => x.CallActivityAsync<string>("E1_SayHello", "London")).ReturnsAsync("Hello London!");
Next the unit test will call HelloSequence.Run
method:
var result = await HelloSequence.Run(durableOrchestrationContextMock.Object);
And finally the output will be validated:
Assert.Equal(3, result.Count);
Assert.Equal("Hello Tokyo!", result[0]);
Assert.Equal("Hello Seattle!", result[1]);
Assert.Equal("Hello London!", result[2]);
After combining all steps, the unit test will have the following code:
[!code-csharpMain]
Activity functions can be unit tested in the same way as non-durable functions. Activity functions don't have a base class for mocking. So the unit tests use the parameter types directly.
In this section the unit test will validate the behavior of the E1_SayHello
Activity function:
[!code-csharpMain]
And the unit test will verify the format of the output:
[!code-csharpMain]
[!div class="nextstepaction"] Learn more about xUnit