From 53360fd0fc421615fed367be67949bf5d81e44a5 Mon Sep 17 00:00:00 2001 From: David Justo Date: Fri, 17 May 2024 11:15:54 -0700 Subject: [PATCH] apply feedback --- .../AzureFunctionsApp/AzureFunctionsApp.cs | 5 ++ .../SampleUnitTests.cs | 49 ++++++++++++++----- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/samples/AzureFunctionsApp/AzureFunctionsApp.cs b/samples/AzureFunctionsApp/AzureFunctionsApp.cs index a23bdb39..3e94aa08 100644 --- a/samples/AzureFunctionsApp/AzureFunctionsApp.cs +++ b/samples/AzureFunctionsApp/AzureFunctionsApp.cs @@ -15,6 +15,9 @@ public static class AzureFunctionsApp public static async Task> RunOrchestrator( [OrchestrationTrigger] TaskOrchestrationContext context) { + ILogger logger = context.CreateReplaySafeLogger(nameof(AzureFunctionsApp)); + logger.LogInformation("Saying hello."); + var outputs = new List(); // Replace name and input with values relevant for your Durable Functions Activity @@ -29,6 +32,8 @@ public static async Task> RunOrchestrator( [Function(nameof(SayHello))] public static string SayHello([ActivityTrigger] string name, FunctionContext executionContext) { + ILogger logger = executionContext.GetLogger("SayHello"); + logger.LogInformation($"Saying hello to {name}."); return $"Hello {name}!"; } diff --git a/samples/AzureFunctionsUnitTests/SampleUnitTests.cs b/samples/AzureFunctionsUnitTests/SampleUnitTests.cs index 5531e934..b780b5ef 100644 --- a/samples/AzureFunctionsUnitTests/SampleUnitTests.cs +++ b/samples/AzureFunctionsUnitTests/SampleUnitTests.cs @@ -16,6 +16,7 @@ namespace Company.Function; // same namespace as the Azure Functions app using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; +using Moq.Protected; public class SampleUnitTests { @@ -25,6 +26,14 @@ public async Task OrchestrationReturnsMultipleGreetings() // create mock orchestration context, and mock ILogger. Mock contextMock = new(); + // a simple ILogger that captures emitted logs in a list + TestLogger logger = new(); + + // The DurableTaskClient CreateReplaySafeLogger API obtains a logger from a protected LoggerFactory property, we mock it here + Mock loggerFactoryMock = new(); + loggerFactoryMock.Setup(x => x.CreateLogger(It.IsAny())).Returns(logger); + contextMock.Protected().Setup("LoggerFactory").Returns(loggerFactoryMock.Object); + // mock activity results // In Moq, optional arguments need to be specified as well. We specify them with It.IsAny(), where T is the type of the optional argument contextMock.Setup(x => x.CallActivityAsync(nameof(AzureFunctionsApp.SayHello), "Tokyo", It.IsAny())) @@ -48,11 +57,29 @@ public async Task OrchestrationReturnsMultipleGreetings() [Fact] public void ActivityReturnsGreeting() { - var contextMock = new Mock(); + Mock contextMock = new(); + + // a simple ILogger that captures emitted logs in a list + TestLogger logger = new(); + + // Mock ILogger service, needed since an ILogger is created in the client via .GetLogger(...); + Mock loggerFactoryMock = new(); + loggerFactoryMock.Setup(x => x.CreateLogger(It.IsAny())).Returns(logger); + Mock instanceServicesMock = new(); + instanceServicesMock.Setup(x => x.GetService(typeof(ILoggerFactory))).Returns(loggerFactoryMock.Object); + + // register mock'ed DI services + var instanceServices = instanceServicesMock.Object; + contextMock.Setup(x => x.InstanceServices).Returns(instanceServices); + var context = contextMock.Object; string output = AzureFunctionsApp.SayHello("Tokyo", context); + // Assert expected logs are emitted + var capturedLogs = logger.CapturedLogs; + Assert.Contains(capturedLogs, log => log.Contains("Saying hello to Tokyo.")); + // assert expected outputs Assert.Equal("Hello Tokyo!", output); } @@ -66,15 +93,15 @@ public async Task ClientReturnsUrls() // we need to mock the FunctionContext and provide it with two mocked services needed by the client // (1) an ILogger service // (2) an ObjectSerializer service, - var contextMock = new Mock(); + Mock contextMock = new(); // a simple ILogger that captures emitted logs in a list - var logger = new TestLogger(); + TestLogger logger = new(); // Mock ILogger service, needed since an ILogger is created in the client via .GetLogger(...); - var loggerFactoryMock = new Mock(); + Mock loggerFactoryMock = new(); loggerFactoryMock.Setup(x => x.CreateLogger(It.IsAny())).Returns(logger); - var instanceServicesMock = new Mock(); + Mock instanceServicesMock = new(); instanceServicesMock.Setup(x => x.GetService(typeof(ILoggerFactory))).Returns(loggerFactoryMock.Object); // mock JsonObjectSerializer service, used during HTTP response serialization @@ -91,7 +118,7 @@ public async Task ClientReturnsUrls() var context = contextMock.Object; // Initialize mock'ed DurableTaskClient with the ability to start orchestrations - var clientMock = new Mock("test client"); + Mock clientMock = new("test client"); clientMock.Setup(x => x.ScheduleNewOrchestrationInstanceAsync(nameof(AzureFunctionsApp), It.IsAny(), It.IsAny(), @@ -100,7 +127,7 @@ public async Task ClientReturnsUrls() var client = clientMock.Object; // Create dummy request object - var request = new TestRequestData(context); + TestRequestData request = new(context); // Invoke the function var output = await AzureFunctionsApp.HttpStart(request, client, context); @@ -111,7 +138,7 @@ public async Task ClientReturnsUrls() // deserialize http output output.Body.Seek(0, SeekOrigin.Begin); - using var reader = new StreamReader(output.Body, Encoding.UTF8); + using StreamReader reader = new(output.Body, Encoding.UTF8); string content = reader.ReadToEnd(); Dictionary? keyValuePairs = JsonSerializer.Deserialize>(content); @@ -136,11 +163,11 @@ public TestRequestData(FunctionContext functionContext) : base(functionContext) public override Stream Body => new MemoryStream(); - public override HttpHeadersCollection Headers => new HttpHeadersCollection(); + public override HttpHeadersCollection Headers => new(); public override IReadOnlyCollection Cookies => new List(); - public override Uri Url => new Uri("http://localhost:8888/myUrl"); + public override Uri Url => new("http://localhost:8888/myUrl"); public override IEnumerable Identities => Enumerable.Empty(); @@ -160,7 +187,7 @@ public TestResponse(FunctionContext functionContext) : base(functionContext) } public override HttpStatusCode StatusCode { get; set; } - public override HttpHeadersCollection Headers { get; set; } = new HttpHeadersCollection(); + public override HttpHeadersCollection Headers { get; set; } = new(); public override HttpCookies Cookies => throw new NotImplementedException();