Skip to content

Commit 098893e

Browse files
authored
Add test output logging to functional tests (#661)
1 parent 36acd3a commit 098893e

File tree

5 files changed

+158
-4
lines changed

5 files changed

+158
-4
lines changed

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ dotnet run --EnableOpenTelemetry=true
117117

118118
## [Tester](./Tester)
119119

120-
The tester shows how to test gRPC services. The unit tests create and test a gRPC service directly. The functional tests show how to use [Microsoft.AspNetCore.TestHost](https://www.nuget.org/packages/Microsoft.AspNetCore.TestHost/) to host a gRPC service with an in-memory test server and call it using a gRPC client.
120+
The tester shows how to test gRPC services. The unit tests create and test a gRPC service directly. The functional tests show how to use [Microsoft.AspNetCore.TestHost](https://www.nuget.org/packages/Microsoft.AspNetCore.TestHost/) to host a gRPC service with an in-memory test server and call it using a gRPC client. The functional tests write client and server logs to the test output.
121121

122122
> **NOTE:** There is a known issue in ASP.NET Core 3.0 that prevents functional testing of bidirectional gRPC methods. Bidirectional gRPC methods can still be unit tested.
123123

examples/Tester/Tests/FunctionalTests/FunctionalTestBase.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,19 @@
1616

1717
#endregion
1818

19+
using System;
1920
using Grpc.Net.Client;
2021
using Microsoft.Extensions.DependencyInjection;
2122
using Microsoft.Extensions.Logging;
2223
using NUnit.Framework;
24+
using Tests.FunctionalTests.Helpers;
2325

2426
namespace Tests.FunctionalTests
2527
{
2628
public class FunctionalTestBase
2729
{
2830
private GrpcChannel? _channel;
31+
private IDisposable? _testContext;
2932

3033
protected GrpcTestFixture<Server.Startup> Fixture { get; private set; } = default!;
3134

@@ -57,5 +60,18 @@ public void OneTimeTearDown()
5760
{
5861
Fixture.Dispose();
5962
}
63+
64+
[SetUp]
65+
public void SetUp()
66+
{
67+
_testContext = Fixture.GetTestContext();
68+
}
69+
70+
[TearDown]
71+
public void TearDown()
72+
{
73+
_testContext?.Dispose();
74+
_channel = null;
75+
}
6076
}
6177
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#region Copyright notice and license
2+
3+
// Copyright 2019 The gRPC Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#endregion
18+
19+
using System;
20+
using Microsoft.Extensions.Logging;
21+
22+
namespace Tests.FunctionalTests.Helpers
23+
{
24+
internal class ForwardingLoggerProvider : ILoggerProvider
25+
{
26+
private readonly LogMessage _logAction;
27+
28+
public ForwardingLoggerProvider(LogMessage logAction)
29+
{
30+
_logAction = logAction;
31+
}
32+
33+
public ILogger CreateLogger(string categoryName)
34+
{
35+
return new ForwardingLogger(categoryName, _logAction);
36+
}
37+
38+
public void Dispose()
39+
{
40+
}
41+
42+
internal class ForwardingLogger : ILogger
43+
{
44+
private readonly string _categoryName;
45+
private readonly LogMessage _logAction;
46+
47+
public ForwardingLogger(string categoryName, LogMessage logAction)
48+
{
49+
_categoryName = categoryName;
50+
_logAction = logAction;
51+
}
52+
53+
public IDisposable? BeginScope<TState>(TState state)
54+
{
55+
return null;
56+
}
57+
58+
public bool IsEnabled(LogLevel logLevel)
59+
{
60+
return true;
61+
}
62+
63+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
64+
{
65+
_logAction(logLevel, _categoryName, eventId, formatter(state, exception), exception);
66+
}
67+
}
68+
}
69+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#region Copyright notice and license
2+
3+
// Copyright 2019 The gRPC Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#endregion
18+
19+
using System;
20+
using System.Diagnostics;
21+
using System.Threading;
22+
using Microsoft.Extensions.Logging;
23+
24+
namespace Tests.FunctionalTests.Helpers
25+
{
26+
internal class GrpcTestContext<TStartup> : IDisposable where TStartup : class
27+
{
28+
private readonly ExecutionContext _executionContext;
29+
private readonly Stopwatch _stopwatch;
30+
private readonly GrpcTestFixture<TStartup> _fixture;
31+
32+
public GrpcTestContext(GrpcTestFixture<TStartup> fixture)
33+
{
34+
_executionContext = ExecutionContext.Capture()!;
35+
_stopwatch = Stopwatch.StartNew();
36+
_fixture = fixture;
37+
_fixture.LoggedMessage += WriteMessage;
38+
}
39+
40+
private void WriteMessage(LogLevel logLevel, string category, EventId eventId, string message, Exception exception)
41+
{
42+
// Log using the passed in execution context.
43+
// In the case of NUnit, console output is only captured by the test
44+
// if it is written in the test's execution context.
45+
ExecutionContext.Run(_executionContext, s =>
46+
{
47+
Console.WriteLine($"{_stopwatch.Elapsed.TotalSeconds:N3}s {category} - {logLevel}: {message}");
48+
}, null);
49+
}
50+
51+
public void Dispose()
52+
{
53+
_fixture.LoggedMessage -= WriteMessage;
54+
_executionContext?.Dispose();
55+
}
56+
}
57+
}

examples/Tester/Tests/FunctionalTests/GrpcTestFixture.cs renamed to examples/Tester/Tests/FunctionalTests/Helpers/GrpcTestFixture.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,35 @@
2323
using Microsoft.AspNetCore.Hosting;
2424
using Microsoft.AspNetCore.TestHost;
2525
using Microsoft.Extensions.DependencyInjection;
26-
using Microsoft.Extensions.DependencyInjection.Extensions;
2726
using Microsoft.Extensions.Hosting;
2827
using Microsoft.Extensions.Logging;
2928

30-
namespace Tests.FunctionalTests
29+
namespace Tests.FunctionalTests.Helpers
3130
{
31+
public delegate void LogMessage(LogLevel logLevel, string categoryName, EventId eventId, string message, Exception exception);
32+
3233
public class GrpcTestFixture<TStartup> : IDisposable where TStartup : class
3334
{
3435
private readonly TestServer _server;
3536
private readonly IHost _host;
3637

38+
public event LogMessage? LoggedMessage;
39+
3740
public GrpcTestFixture() : this(null) { }
3841

3942
public GrpcTestFixture(Action<IServiceCollection>? initialConfigureServices)
4043
{
4144
LoggerFactory = new LoggerFactory();
45+
LoggerFactory.AddProvider(new ForwardingLoggerProvider((logLevel, category, eventId, message, exception) =>
46+
{
47+
LoggedMessage?.Invoke(logLevel, category, eventId, message, exception);
48+
}));
4249

4350
var builder = new HostBuilder()
4451
.ConfigureServices(services =>
4552
{
4653
initialConfigureServices?.Invoke(services);
47-
services.TryAddSingleton<ILoggerFactory>(LoggerFactory);
54+
services.AddSingleton<ILoggerFactory>(LoggerFactory);
4855
})
4956
.ConfigureWebHostDefaults(webHost =>
5057
{
@@ -87,5 +94,10 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
8794
return response;
8895
}
8996
}
97+
98+
public IDisposable GetTestContext()
99+
{
100+
return new GrpcTestContext<TStartup>(this);
101+
}
90102
}
91103
}

0 commit comments

Comments
 (0)