diff --git a/Logging.XUnit.sln b/Logging.XUnit.sln
index 534abe05..8f780a41 100644
--- a/Logging.XUnit.sln
+++ b/Logging.XUnit.sln
@@ -52,6 +52,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MartinCostello.Logging.XUni
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MartinCostello.Logging.XUnit", "src\Logging.XUnit\MartinCostello.Logging.XUnit.csproj", "{BB443063-F523-474D-83E3-D5FF5B075950}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "tests\SampleApp\SampleApp.csproj", "{B690F271-3B5D-4975-A607-AED1768595B1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -66,6 +68,10 @@ Global
{BB443063-F523-474D-83E3-D5FF5B075950}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB443063-F523-474D-83E3-D5FF5B075950}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB443063-F523-474D-83E3-D5FF5B075950}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B690F271-3B5D-4975-A607-AED1768595B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B690F271-3B5D-4975-A607-AED1768595B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B690F271-3B5D-4975-A607-AED1768595B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B690F271-3B5D-4975-A607-AED1768595B1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -76,6 +82,7 @@ Global
{F37263E7-6656-4A2B-B02E-538B5F9970BD} = {D0426D09-1FF8-4E1F-A9AF-38DDEE5D7CCA}
{331BC69B-DFFC-4174-8A97-6335BF6D86CF} = {278BCCB1-39B2-46DB-9395-7F85995A6132}
{BB443063-F523-474D-83E3-D5FF5B075950} = {2684B19D-7D49-4099-8BD2-4D281455EB29}
+ {B690F271-3B5D-4975-A607-AED1768595B1} = {278BCCB1-39B2-46DB-9395-7F85995A6132}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3B9E157C-5E92-4357-B233-281B4530EABD}
diff --git a/src/Logging.XUnit/AmbientTestOutputHelperAccessor.cs b/src/Logging.XUnit/AmbientTestOutputHelperAccessor.cs
new file mode 100644
index 00000000..d1b22b61
--- /dev/null
+++ b/src/Logging.XUnit/AmbientTestOutputHelperAccessor.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Martin Costello, 2018. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Threading;
+using Xunit.Abstractions;
+
+namespace MartinCostello.Logging.XUnit
+{
+ ///
+ /// A class representing an implementation of that
+ /// stores the as an asynchronous local value. This class cannot be inherited.
+ ///
+ internal sealed class AmbientTestOutputHelperAccessor : ITestOutputHelperAccessor
+ {
+ ///
+ /// A backing field for the for the current thread.
+ ///
+ private static readonly AsyncLocal _current = new AsyncLocal();
+
+#pragma warning disable CA1822
+ ///
+ /// Gets or sets the current .
+ ///
+ public ITestOutputHelper OutputHelper
+ {
+ get { return _current.Value; }
+ set { _current.Value = value; }
+ }
+#pragma warning restore CA1822
+ }
+}
diff --git a/src/Logging.XUnit/ITestOutputHelperAccessor.cs b/src/Logging.XUnit/ITestOutputHelperAccessor.cs
new file mode 100644
index 00000000..f9a517b8
--- /dev/null
+++ b/src/Logging.XUnit/ITestOutputHelperAccessor.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Martin Costello, 2018. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using Xunit.Abstractions;
+
+namespace MartinCostello.Logging.XUnit
+{
+ ///
+ /// Defines a property for accessing an .
+ ///
+ public interface ITestOutputHelperAccessor
+ {
+ ///
+ /// Gets or sets the to use.
+ ///
+ ITestOutputHelper OutputHelper { get; set; }
+ }
+}
diff --git a/src/Logging.XUnit/TestOutputHelperAccessor.cs b/src/Logging.XUnit/TestOutputHelperAccessor.cs
new file mode 100644
index 00000000..e4e6257c
--- /dev/null
+++ b/src/Logging.XUnit/TestOutputHelperAccessor.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Martin Costello, 2018. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System;
+using Xunit.Abstractions;
+
+namespace MartinCostello.Logging.XUnit
+{
+ ///
+ /// A class representing the default implementation of . This class cannot be inherited.
+ ///
+ internal sealed class TestOutputHelperAccessor : ITestOutputHelperAccessor
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to use.
+ ///
+ /// is .
+ ///
+ internal TestOutputHelperAccessor(ITestOutputHelper outputHelper)
+ {
+ OutputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
+ }
+
+ ///
+ /// Gets or sets the current .
+ ///
+ public ITestOutputHelper OutputHelper { get; set; }
+ }
+}
diff --git a/src/Logging.XUnit/XUnitLogger.cs b/src/Logging.XUnit/XUnitLogger.cs
index 435ffeb9..85ab8760 100644
--- a/src/Logging.XUnit/XUnitLogger.cs
+++ b/src/Logging.XUnit/XUnitLogger.cs
@@ -37,9 +37,9 @@ public class XUnitLogger : ILogger
private static StringBuilder _logBuilder;
///
- /// The to use. This field is read-only.
+ /// The to use. This field is read-only.
///
- private readonly ITestOutputHelper _outputHelper;
+ private readonly ITestOutputHelperAccessor _accessor;
///
/// Gets or sets the filter to use.
@@ -56,9 +56,23 @@ public class XUnitLogger : ILogger
/// or is .
///
public XUnitLogger(string name, ITestOutputHelper outputHelper, XUnitLoggerOptions options)
+ : this(name, new TestOutputHelperAccessor(outputHelper), options)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name for messages produced by the logger.
+ /// The to use.
+ /// The to use.
+ ///
+ /// or is .
+ ///
+ public XUnitLogger(string name, ITestOutputHelperAccessor accessor, XUnitLoggerOptions options)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
- _outputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
+ _accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
Filter = options?.Filter ?? ((category, logLevel) => true);
IncludeScopes = options?.IncludeScopes ?? false;
@@ -143,6 +157,13 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
/// The exception related to this message.
public virtual void WriteMessage(LogLevel logLevel, int eventId, string message, Exception exception)
{
+ ITestOutputHelper outputHelper = _accessor.OutputHelper;
+
+ if (outputHelper == null)
+ {
+ return;
+ }
+
StringBuilder logBuilder = _logBuilder;
_logBuilder = null;
@@ -186,7 +207,7 @@ public virtual void WriteMessage(LogLevel logLevel, int eventId, string message,
}
string formatted = logBuilder.ToString();
- _outputHelper.WriteLine($"[{Clock():u}] {logLevelString}{formatted}");
+ outputHelper.WriteLine($"[{Clock():u}] {logLevelString}{formatted}");
logBuilder.Clear();
diff --git a/src/Logging.XUnit/XUnitLoggerExtensions.cs b/src/Logging.XUnit/XUnitLoggerExtensions.cs
index 56bca335..f87745a8 100644
--- a/src/Logging.XUnit/XUnitLoggerExtensions.cs
+++ b/src/Logging.XUnit/XUnitLoggerExtensions.cs
@@ -4,6 +4,7 @@
using System;
using System.ComponentModel;
using MartinCostello.Logging.XUnit;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Xunit.Abstractions;
namespace Microsoft.Extensions.Logging
@@ -14,6 +15,91 @@ namespace Microsoft.Extensions.Logging
[EditorBrowsable(EditorBrowsableState.Never)]
public static class XUnitLoggerExtensions
{
+ ///
+ /// Adds an xunit logger to the logging builder.
+ ///
+ /// The to use.
+ ///
+ /// The instance of specified by .
+ ///
+ ///
+ /// is .
+ ///
+ public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ return builder.AddXUnit(new AmbientTestOutputHelperAccessor(), (_) => { });
+ }
+
+ ///
+ /// Adds an xunit logger to the logging builder.
+ ///
+ /// The to use.
+ /// The to use.
+ ///
+ /// The instance of specified by .
+ ///
+ ///
+ /// or is .
+ ///
+ public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, ITestOutputHelperAccessor accessor)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (accessor == null)
+ {
+ throw new ArgumentNullException(nameof(accessor));
+ }
+
+ return builder.AddXUnit(accessor, (_) => { });
+ }
+
+ ///
+ /// Adds an xunit logger to the logging builder.
+ ///
+ /// The to use.
+ /// The to use.
+ /// A delegate to a method to use to configure the logging options.
+ ///
+ /// The instance of specified by .
+ ///
+ ///
+ /// , OR is .
+ ///
+ public static ILoggingBuilder AddXUnit(this ILoggingBuilder builder, ITestOutputHelperAccessor accessor, Action configure)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (accessor == null)
+ {
+ throw new ArgumentNullException(nameof(accessor));
+ }
+
+ if (configure == null)
+ {
+ throw new ArgumentNullException(nameof(configure));
+ }
+
+ var options = new XUnitLoggerOptions();
+
+ configure(options);
+
+ builder.AddProvider(new XUnitLoggerProvider(accessor, options));
+ builder.Services.TryAddSingleton(accessor);
+
+ return builder;
+ }
+
///
/// Adds an xunit logger to the logging builder.
///
diff --git a/src/Logging.XUnit/XUnitLoggerProvider.cs b/src/Logging.XUnit/XUnitLoggerProvider.cs
index d5307ac1..c6ccaab5 100644
--- a/src/Logging.XUnit/XUnitLoggerProvider.cs
+++ b/src/Logging.XUnit/XUnitLoggerProvider.cs
@@ -13,9 +13,9 @@ namespace MartinCostello.Logging.XUnit
public class XUnitLoggerProvider : ILoggerProvider
{
///
- /// The to use. This field is readonly.
+ /// The to use. This field is readonly.
///
- private readonly ITestOutputHelper _outputHelper;
+ private readonly ITestOutputHelperAccessor _accessor;
///
/// The to use. This field is readonly.
@@ -31,8 +31,21 @@ public class XUnitLoggerProvider : ILoggerProvider
/// or is .
///
public XUnitLoggerProvider(ITestOutputHelper outputHelper, XUnitLoggerOptions options)
+ : this(new TestOutputHelperAccessor(outputHelper), options)
{
- _outputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to use.
+ /// The options to use for logging to xunit.
+ ///
+ /// or is .
+ ///
+ public XUnitLoggerProvider(ITestOutputHelperAccessor accessor, XUnitLoggerOptions options)
+ {
+ _accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
@@ -45,7 +58,7 @@ public XUnitLoggerProvider(ITestOutputHelper outputHelper, XUnitLoggerOptions op
}
///
- public virtual ILogger CreateLogger(string categoryName) => new XUnitLogger(categoryName, _outputHelper, _options);
+ public virtual ILogger CreateLogger(string categoryName) => new XUnitLogger(categoryName, _accessor, _options);
///
public void Dispose()
diff --git a/tests/Logging.XUnit.Tests/Integration/HttpApplicationTests.cs b/tests/Logging.XUnit.Tests/Integration/HttpApplicationTests.cs
new file mode 100644
index 00000000..4a183ca7
--- /dev/null
+++ b/tests/Logging.XUnit.Tests/Integration/HttpApplicationTests.cs
@@ -0,0 +1,104 @@
+// Copyright (c) Martin Costello, 2018. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Shouldly;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace MartinCostello.Logging.XUnit.Integration
+{
+ [Collection(HttpServerCollection.Name)]
+ public sealed class HttpApplicationTests : IDisposable
+ {
+ public HttpApplicationTests(HttpServerFixture fixture, ITestOutputHelper outputHelper)
+ {
+ Fixture = fixture;
+ Fixture.SetOutputHelper(outputHelper);
+ }
+
+ private HttpServerFixture Fixture { get; }
+
+ public void Dispose()
+ {
+ Fixture.ClearOutputHelper();
+ }
+
+ [Fact]
+ public async Task Http_Get_Many()
+ {
+ // Arrange
+ using (var httpClient = Fixture.CreateClient())
+ {
+ // Act
+ using (var response = await httpClient.GetAsync("api/values"))
+ {
+ // Assert
+ response.IsSuccessStatusCode.ShouldBeTrue();
+ }
+ }
+ }
+
+ [Fact]
+ public async Task Http_Get_Single()
+ {
+ // Arrange
+ using (var httpClient = Fixture.CreateClient())
+ {
+ // Act
+ using (var response = await httpClient.GetAsync("api/values/a"))
+ {
+ // Assert
+ response.IsSuccessStatusCode.ShouldBeTrue();
+ }
+ }
+ }
+
+ [Fact]
+ public async Task Http_Post()
+ {
+ // Arrange
+ using (var httpClient = Fixture.CreateClient())
+ {
+ // Act
+ using (var response = await httpClient.PostAsJsonAsync("api/values", "d"))
+ {
+ // Assert
+ response.IsSuccessStatusCode.ShouldBeTrue();
+ }
+ }
+ }
+
+ [Fact]
+ public async Task Http_Put()
+ {
+ // Arrange
+ using (var httpClient = Fixture.CreateClient())
+ {
+ // Act
+ using (var response = await httpClient.PutAsJsonAsync("api/values/d", "d"))
+ {
+ // Assert
+ response.IsSuccessStatusCode.ShouldBeTrue();
+ }
+ }
+ }
+
+ [Fact]
+ public async Task Http_Delete()
+ {
+ // Arrange
+ using (var httpClient = Fixture.CreateClient())
+ {
+ // Act
+ using (var response = await httpClient.DeleteAsync("api/values/d"))
+ {
+ // Assert
+ response.IsSuccessStatusCode.ShouldBeTrue();
+ }
+ }
+ }
+ }
+}
diff --git a/tests/Logging.XUnit.Tests/Integration/HttpServerCollection.cs b/tests/Logging.XUnit.Tests/Integration/HttpServerCollection.cs
new file mode 100644
index 00000000..7bee32ca
--- /dev/null
+++ b/tests/Logging.XUnit.Tests/Integration/HttpServerCollection.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Martin Costello, 2018. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using Xunit;
+
+namespace MartinCostello.Logging.XUnit.Integration
+{
+ ///
+ /// A class representing the collection fixture for an HTTP server. This class cannot be inherited.
+ ///
+ [CollectionDefinition(Name)]
+ public sealed class HttpServerCollection : ICollectionFixture
+ {
+ ///
+ /// The name of the test fixture.
+ ///
+ public const string Name = "HTTP server collection";
+ }
+}
diff --git a/tests/Logging.XUnit.Tests/Integration/HttpServerFixture.cs b/tests/Logging.XUnit.Tests/Integration/HttpServerFixture.cs
new file mode 100644
index 00000000..132ca266
--- /dev/null
+++ b/tests/Logging.XUnit.Tests/Integration/HttpServerFixture.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Martin Costello, 2018. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using SampleApp;
+using Xunit.Abstractions;
+
+namespace MartinCostello.Logging.XUnit.Integration
+{
+ ///
+ /// A test fixture representing an HTTP server hosting the sample application. This class cannot be inherited.
+ ///
+ public sealed class HttpServerFixture : WebApplicationFactory
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public HttpServerFixture()
+ : base()
+ {
+ // HACK Force HTTP server startup
+ using (CreateDefaultClient())
+ {
+ }
+ }
+
+ ///
+ /// Clears the current .
+ ///
+ public void ClearOutputHelper()
+ {
+ Server.Host.Services.GetRequiredService().OutputHelper = null;
+ }
+
+ ///
+ /// Sets the to use.
+ ///
+ /// The to use.
+ public void SetOutputHelper(ITestOutputHelper value)
+ {
+ Server.Host.Services.GetRequiredService().OutputHelper = value;
+ }
+
+ ///
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
+ {
+ builder.ConfigureLogging((p) => p.AddXUnit());
+ }
+ }
+}
diff --git a/tests/Logging.XUnit.Tests/IntegrationTests.cs b/tests/Logging.XUnit.Tests/IntegrationTests.cs
index b8e109a6..2aed796c 100644
--- a/tests/Logging.XUnit.Tests/IntegrationTests.cs
+++ b/tests/Logging.XUnit.Tests/IntegrationTests.cs
@@ -13,7 +13,7 @@ namespace MartinCostello.Logging.XUnit
public static class IntegrationTests
{
[Fact]
- public static void Can_Configure_xunit_For_ILoggerBuilder()
+ public static void Can_Configure_xunit_For_ILoggerBuilder_TestOutputHelper()
{
// Arrange
var mock = new Mock();
@@ -28,7 +28,7 @@ public static void Can_Configure_xunit_For_ILoggerBuilder()
}
[Fact]
- public static void Can_Configure_xunit_For_ILoggerBuilder_With_Configuration()
+ public static void Can_Configure_xunit_For_ILoggerBuilder_TestOutputHelper_With_Configuration()
{
// Arrange
var mock = new Mock();
@@ -49,6 +49,62 @@ public static void Can_Configure_xunit_For_ILoggerBuilder_With_Configuration()
mock.Verify((p) => p.WriteLine(It.IsNotNull()), Times.Once());
}
+ [Fact]
+ public static void Can_Configure_xunit_For_ILoggerBuilderAccessor_TestOutputHelper()
+ {
+ // Arrange
+ var mockOutputHelper = new Mock();
+ var outputHelper = mockOutputHelper.Object;
+
+ var mockAccessor = new Mock();
+
+ mockAccessor
+ .Setup((p) => p.OutputHelper)
+ .Returns(outputHelper);
+
+ var accessor = mockAccessor.Object;
+
+ var logger = BootstrapBuilder((builder) => builder.AddXUnit(accessor));
+
+ // Act
+ logger.LogError("This is a brand new problem, a problem without any clues.");
+ logger.LogInformation("If you know the clues, it's easy to get through.");
+
+ // Assert
+ mockOutputHelper.Verify((p) => p.WriteLine(It.IsNotNull()), Times.Exactly(2));
+ }
+
+ [Fact]
+ public static void Can_Configure_xunit_For_ILoggerBuilder_TestOutputHelperAccessor_With_Configuration()
+ {
+ // Arrange
+ var mockOutputHelper = new Mock();
+ var outputHelper = mockOutputHelper.Object;
+
+ var mockAccessor = new Mock();
+
+ mockAccessor
+ .Setup((p) => p.OutputHelper)
+ .Returns(outputHelper);
+
+ var accessor = mockAccessor.Object;
+
+ var logger = BootstrapBuilder(
+ (builder) =>
+ {
+ builder.AddXUnit(
+ mockOutputHelper.Object,
+ (options) => options.Filter = (_, level) => level >= LogLevel.Error);
+ });
+
+ // Act
+ logger.LogError("This is a brand new problem, a problem without any clues.");
+ logger.LogTrace("If you know the clues, it's easy to get through.");
+
+ // Assert
+ mockOutputHelper.Verify((p) => p.WriteLine(It.IsNotNull()), Times.Once());
+ }
+
[Fact]
public static void Can_Configure_xunit_For_ILoggerFactory()
{
@@ -157,6 +213,28 @@ public static void Can_Configure_xunit_For_ILoggerFactory_With_Configure_Options
mock.Verify((p) => p.WriteLine(It.IsNotNull()), Times.Once());
}
+ [Fact]
+ public static void Can_Configure_xunit_For_ILoggerBuilder()
+ {
+ // Arrange
+ var serviceProvider = new ServiceCollection()
+ .AddLogging((builder) => builder.AddXUnit())
+ .BuildServiceProvider();
+
+ var mock = new Mock();
+
+ serviceProvider.GetRequiredService().OutputHelper = mock.Object;
+
+ var logger = serviceProvider.GetRequiredService>();
+
+ // Act
+ logger.LogError("This is a brand new problem, a problem without any clues.");
+ logger.LogInformation("If you know the clues, it's easy to get through.");
+
+ // Assert
+ mock.Verify((p) => p.WriteLine(It.IsNotNull()), Times.Exactly(2));
+ }
+
private static ILogger BootstrapBuilder(Action configure)
{
return new ServiceCollection()
diff --git a/tests/Logging.XUnit.Tests/MartinCostello.Logging.XUnit.Tests.csproj b/tests/Logging.XUnit.Tests/MartinCostello.Logging.XUnit.Tests.csproj
index a2ae0ee4..d8397156 100644
--- a/tests/Logging.XUnit.Tests/MartinCostello.Logging.XUnit.Tests.csproj
+++ b/tests/Logging.XUnit.Tests/MartinCostello.Logging.XUnit.Tests.csproj
@@ -1,8 +1,8 @@
-
+
Tests for MartinCostello.Logging.XUnit.
false
- $(NoWarn);CA1707
+ $(NoWarn);CA1707;CA2007;CA2234
true
MartinCostello.Logging.XUnit
$(Description)
@@ -13,8 +13,10 @@
+
+
diff --git a/tests/Logging.XUnit.Tests/XUnitLoggerExtensionsTests.cs b/tests/Logging.XUnit.Tests/XUnitLoggerExtensionsTests.cs
index 60de11eb..fad3e2ea 100644
--- a/tests/Logging.XUnit.Tests/XUnitLoggerExtensionsTests.cs
+++ b/tests/Logging.XUnit.Tests/XUnitLoggerExtensionsTests.cs
@@ -18,13 +18,20 @@ public static void AddXUnit_For_ILoggerBuilder_Validates_Parameters()
// Arrange
var builder = Mock.Of();
var outputHelper = Mock.Of();
+ var accessor = Mock.Of();
// Act and Assert
+ Assert.Throws("builder", () => (null as ILoggingBuilder).AddXUnit());
Assert.Throws("builder", () => (null as ILoggingBuilder).AddXUnit(outputHelper));
Assert.Throws("builder", () => (null as ILoggingBuilder).AddXUnit(outputHelper, ConfigureAction));
+ Assert.Throws("builder", () => (null as ILoggingBuilder).AddXUnit(accessor));
+ Assert.Throws("builder", () => (null as ILoggingBuilder).AddXUnit(accessor, ConfigureAction));
+ Assert.Throws("accessor", () => builder.AddXUnit(null as ITestOutputHelperAccessor));
+ Assert.Throws("accessor", () => builder.AddXUnit(null as ITestOutputHelperAccessor, ConfigureAction));
Assert.Throws("outputHelper", () => builder.AddXUnit(null as ITestOutputHelper));
- Assert.Throws("outputHelper", () => builder.AddXUnit(null, ConfigureAction));
+ Assert.Throws("outputHelper", () => builder.AddXUnit(null as ITestOutputHelper, ConfigureAction));
Assert.Throws("configure", () => builder.AddXUnit(outputHelper, null as Action));
+ Assert.Throws("configure", () => builder.AddXUnit(accessor, null as Action));
}
[Fact]
diff --git a/tests/Logging.XUnit.Tests/XUnitLoggerProviderTests.cs b/tests/Logging.XUnit.Tests/XUnitLoggerProviderTests.cs
index c6c6fcd9..37e4eb35 100644
--- a/tests/Logging.XUnit.Tests/XUnitLoggerProviderTests.cs
+++ b/tests/Logging.XUnit.Tests/XUnitLoggerProviderTests.cs
@@ -17,11 +17,14 @@ public static void XUnitLoggerProvider_Constructor_Validates_Parameters()
{
// Arrange
var outputHelper = Mock.Of();
+ var accessor = Mock.Of();
var options = new XUnitLoggerOptions();
// Act and Assert
- Assert.Throws("outputHelper", () => new XUnitLoggerProvider(null, options));
+ Assert.Throws("outputHelper", () => new XUnitLoggerProvider(null as ITestOutputHelper, options));
+ Assert.Throws("accessor", () => new XUnitLoggerProvider(null as ITestOutputHelperAccessor, options));
Assert.Throws("options", () => new XUnitLoggerProvider(outputHelper, null));
+ Assert.Throws("options", () => new XUnitLoggerProvider(accessor, null));
}
[Fact]
diff --git a/tests/Logging.XUnit.Tests/XUnitLoggerTests.cs b/tests/Logging.XUnit.Tests/XUnitLoggerTests.cs
index 1d6ba5ac..6b937c5a 100644
--- a/tests/Logging.XUnit.Tests/XUnitLoggerTests.cs
+++ b/tests/Logging.XUnit.Tests/XUnitLoggerTests.cs
@@ -26,7 +26,8 @@ public static void XUnitLogger_Validates_Parameters()
// Act and Assert
Assert.Throws("name", () => new XUnitLogger(null, outputHelper, options));
- Assert.Throws("outputHelper", () => new XUnitLogger(name, null, options));
+ Assert.Throws("outputHelper", () => new XUnitLogger(name, null as ITestOutputHelper, options));
+ Assert.Throws("accessor", () => new XUnitLogger(name, null as ITestOutputHelperAccessor, options));
// Arrange
var logger = new XUnitLogger(name, outputHelper, options);
@@ -228,6 +229,24 @@ public static void XUnitLogger_Log_Does_Nothing_If_Empty_Message_And_No_Exceptio
mock.Verify((p) => p.WriteLine(It.IsAny()), Times.Never());
}
+ [Fact]
+ public static void XUnitLogger_Log_Does_Nothing_If_No_OutputHelper()
+ {
+ // Arrange
+ string name = "MyName";
+ var accessor = Mock.Of();
+
+ var options = new XUnitLoggerOptions()
+ {
+ Filter = FilterTrue,
+ };
+
+ var logger = new XUnitLogger(name, accessor, options);
+
+ // Act (no Assert)
+ logger.Log(LogLevel.Information, new EventId(2), "state", null, Formatter);
+ }
+
[Fact]
public static void XUnitLogger_Log_Logs_Message_If_Only_Exception()
{
diff --git a/tests/SampleApp/Controllers/ValuesController.cs b/tests/SampleApp/Controllers/ValuesController.cs
new file mode 100644
index 00000000..05419dce
--- /dev/null
+++ b/tests/SampleApp/Controllers/ValuesController.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Martin Costello, 2018. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc;
+
+namespace SampleApp.Controllers
+{
+ [ApiController]
+ [Route("api/[controller]")]
+ public class ValuesController : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult> Get()
+ {
+ return new string[] { "a", "b", "c" };
+ }
+
+ [HttpGet("{id}")]
+ public ActionResult Get(string id)
+ {
+ return "value";
+ }
+
+ [HttpPost]
+ public void Post([FromBody] string value)
+ {
+ }
+
+ [HttpPut("{id}")]
+ public void Put(string id, [FromBody] string value)
+ {
+ }
+
+ [HttpDelete("{id}")]
+ public void Delete(string id)
+ {
+ }
+ }
+}
diff --git a/tests/SampleApp/Program.cs b/tests/SampleApp/Program.cs
new file mode 100644
index 00000000..b06d8246
--- /dev/null
+++ b/tests/SampleApp/Program.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Martin Costello, 2018. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+
+namespace SampleApp
+{
+ public static class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateWebHostBuilder(args).Build().Run();
+ }
+
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
+ WebHost.CreateDefaultBuilder(args)
+ .UseStartup();
+ }
+}
diff --git a/tests/SampleApp/Properties/launchSettings.json b/tests/SampleApp/Properties/launchSettings.json
new file mode 100644
index 00000000..4aad6877
--- /dev/null
+++ b/tests/SampleApp/Properties/launchSettings.json
@@ -0,0 +1,30 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:52657",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": false,
+ "launchUrl": "api/values",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "SampleApp": {
+ "commandName": "SampleApp",
+ "launchBrowser": false,
+ "launchUrl": "api/values",
+ "applicationUrl": "http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/tests/SampleApp/SampleApp.csproj b/tests/SampleApp/SampleApp.csproj
new file mode 100644
index 00000000..0125f52e
--- /dev/null
+++ b/tests/SampleApp/SampleApp.csproj
@@ -0,0 +1,10 @@
+
+
+ false
+ $(NoWarn);CA1801;CA1822
+ netcoreapp2.1
+
+
+
+
+
diff --git a/tests/SampleApp/Startup.cs b/tests/SampleApp/Startup.cs
new file mode 100644
index 00000000..04773acc
--- /dev/null
+++ b/tests/SampleApp/Startup.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Martin Costello, 2018. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace SampleApp
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+
+ app.UseMvc();
+ }
+ }
+}
diff --git a/tests/SampleApp/appsettings.Development.json b/tests/SampleApp/appsettings.Development.json
new file mode 100644
index 00000000..e203e940
--- /dev/null
+++ b/tests/SampleApp/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "System": "Information",
+ "Microsoft": "Information"
+ }
+ }
+}
diff --git a/tests/SampleApp/appsettings.json b/tests/SampleApp/appsettings.json
new file mode 100644
index 00000000..def9159a
--- /dev/null
+++ b/tests/SampleApp/appsettings.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}