All Projects → SimonCropp → XunitContext

SimonCropp / XunitContext

Licence: MIT license
Extends xUnit to expose extra context and simplify logging

Programming Languages

C#
18002 projects

Projects that are alternatives of or similar to XunitContext

testcontainers-dotnet
dotnet port of testcontainers-java
Stars: ✭ 22 (-82.68%)
Mutual labels:  xunit
xunit-unity-runner
Run Xunit tests on Unity player
Stars: ✭ 13 (-89.76%)
Mutual labels:  xunit
Specflow
#1 .NET BDD Framework. SpecFlow automates your testing & works with your existing code. Find Bugs before they happen. Behavior Driven Development helps developers, testers, and business representatives to get a better understanding of their collaboration
Stars: ✭ 1,827 (+1338.58%)
Mutual labels:  xunit
test-class
Test::Class - an xUnit testing framework for Perl 5.x
Stars: ✭ 18 (-85.83%)
Mutual labels:  xunit
pact-workshop-dotnet-core-v1
A workshop for Pact using .NET Core
Stars: ✭ 66 (-48.03%)
Mutual labels:  xunit
mpunit
Mini PHP xUnit Testing Framework
Stars: ✭ 15 (-88.19%)
Mutual labels:  xunit
FakeItEasy.AutoFakeIt
A very simple, yet flexible, "AutoFaker" for FakeItEasy to easily auto generate classes with faked dependencies.
Stars: ✭ 15 (-88.19%)
Mutual labels:  xunit
resharper-xunit-templates
ReSharper Live Templates for xUnit.net
Stars: ✭ 18 (-85.83%)
Mutual labels:  xunit
Kodkod
https://github.com/alirizaadiyahsi/Nucleus Web API layered architecture startup template with ASP.NET Core 2.1, EF Core 2.1 and Vue Client
Stars: ✭ 45 (-64.57%)
Mutual labels:  xunit
Cake
🍰 Cake (C# Make) is a cross platform build automation system.
Stars: ✭ 3,154 (+2383.46%)
Mutual labels:  xunit
contoso-university
Contoso University demo using asp net core and related technologies
Stars: ✭ 42 (-66.93%)
Mutual labels:  xunit
xunit-orderer
Implementation of ITestCaseOrderer enforcing xUnit to run the facts in strict order
Stars: ✭ 15 (-88.19%)
Mutual labels:  xunit
xRetry
Retry running tests via Xunit and Specflow
Stars: ✭ 15 (-88.19%)
Mutual labels:  xunit
Xunit.Categories
Friendlier attributes to help categorize your tests
Stars: ✭ 85 (-33.07%)
Mutual labels:  xunit
Fluentassertions
A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests. Targets .NET Framework 4.7, .NET Core 2.1 and 3.0, as well as .NET Standard 2.0 and 2.1. Supports the unit test frameworks MSTest2, NUnit3, XUnit2, MSpec, and NSpec3.
Stars: ✭ 2,449 (+1828.35%)
Mutual labels:  xunit
kekiri
A .NET framework that supports writing low-ceremony BDD tests using Gherkin language
Stars: ✭ 19 (-85.04%)
Mutual labels:  xunit
tap-xunit
TAP to xUnit XML converter
Stars: ✭ 40 (-68.5%)
Mutual labels:  xunit
bUnit
bUnit is a testing library for Blazor components that make tests look, feel, and runs like regular unit tests. bUnit makes it easy to render and control a component under test’s life-cycle, pass parameter and inject services into it, trigger event handlers, and verify the rendered markup from the component using a built-in semantic HTML comparer.
Stars: ✭ 857 (+574.8%)
Mutual labels:  xunit
Bats Core
Bash Automated Testing System
Stars: ✭ 2,820 (+2120.47%)
Mutual labels:  xunit
Xunit
xUnit.net is a free, open source, community-focused unit testing tool for the .NET Framework.
Stars: ✭ 3,120 (+2356.69%)
Mutual labels:  xunit

XunitContext

Build status NuGet Status

Extends xUnit to expose extra context and simplify logging.

Redirects Trace.Write, Debug.Write, and Console.Write and Console.Error.Write to ITestOutputHelper. Also provides static access to the current ITestOutputHelper for use within testing utility methods.

Uses AsyncLocal to track state.

NuGet package

https://nuget.org/packages/XunitContext/

ClassBeingTested

static class ClassBeingTested
{
    public static void Method()
    {
        Trace.WriteLine("From Trace");
        Console.WriteLine("From Console");
        Debug.WriteLine("From Debug");
        Console.Error.WriteLine("From Console Error");
    }
}

snippet source | anchor

XunitContextBase

XunitContextBase is an abstract base class for tests. It exposes logging methods for use from unit tests, and handle the flushing of logs in its Dispose method. XunitContextBase is actually a thin wrapper over XunitContext. XunitContexts Write* methods can also be use inside a test inheriting from XunitContextBase.

public class TestBaseSample  :
    XunitContextBase
{
    [Fact]
    public void Write_lines()
    {
        WriteLine("From Test");
        ClassBeingTested.Method();

        var logs = XunitContext.Logs;

        Assert.Contains("From Test", logs);
        Assert.Contains("From Trace", logs);
        Assert.Contains("From Debug", logs);
        Assert.Contains("From Console", logs);
        Assert.Contains("From Console Error", logs);
    }

    public TestBaseSample(ITestOutputHelper output) :
        base(output)
    {
    }
}

snippet source | anchor

Logging

XunitContext provides static access to the logging state for tests. It exposes logging methods for use from unit tests, however registration of ITestOutputHelper and flushing of logs must be handled explicitly.

public class XunitLoggerSample :
    IDisposable
{
    [Fact]
    public void Usage()
    {
        XunitContext.WriteLine("From Test");

        ClassBeingTested.Method();

        var logs = XunitContext.Logs;

        Assert.Contains("From Test", logs);
        Assert.Contains("From Trace", logs);
        Assert.Contains("From Debug", logs);
        Assert.Contains("From Console", logs);
        Assert.Contains("From Console Error", logs);
    }

    public XunitLoggerSample(ITestOutputHelper testOutput) =>
        XunitContext.Register(testOutput);

    public void Dispose() =>
        XunitContext.Flush();
}

snippet source | anchor

XunitContext redirects Trace.Write, Console.Write, and Debug.Write in its static constructor.

Trace.Listeners.Clear();
Trace.Listeners.Add(new TraceListener());
#if (NETSTANDARD)
DebugPoker.Overwrite(
    text =>
    {
        if (string.IsNullOrEmpty(text))
        {
            return;
        }

        if (text.EndsWith(Environment.NewLine))
        {
            WriteLine(text.TrimTrailingNewline());
            return;
        }

        Write(text);
    });
#else
Debug.Listeners.Clear();
Debug.Listeners.Add(new TraceListener());
#endif
TestWriter writer = new();
Console.SetOut(writer);
Console.SetError(writer);

snippet source | anchor

These API calls are then routed to the correct xUnit ITestOutputHelper via a static AsyncLocal.

Logging Libs

Approaches to routing common logging libraries to Diagnostics.Trace:

Filters

XunitContext.Filters can be used to filter out unwanted lines:

public class FilterSample :
    XunitContextBase
{
    static FilterSample() =>
        Filters.Add(x => x != null && !x.Contains("ignored"));

    [Fact]
    public void Write_lines()
    {
        WriteLine("first");
        WriteLine("with ignored string");
        WriteLine("last");
        var logs = XunitContext.Logs;

        Assert.Contains("first", logs);
        Assert.DoesNotContain("with ignored string", logs);
        Assert.Contains("last", logs);
    }

    public FilterSample(ITestOutputHelper output) :
        base(output)
    {
    }
}

snippet source | anchor

Filters are static and shared for all tests.

Context

For every tests there is a contextual API to perform several operations.

  • Context.TestOutput: Access to ITestOutputHelper.
  • Context.Write and Context.WriteLine: Write to the current log.
  • Context.LogMessages: Access to all log message for the current test.
  • Counters: Provide access in predicable and incrementing values for the following types: Guid, Int, Long, UInt, and ULong.
  • Context.Test: Access to the current ITest.
  • Context.SourceFile: Access to the file path for the current test.
  • Context.SourceDirectory: Access to the directory path for the current test.
  • Context.SolutionDirectory: The current solution directory. Obtained by walking up the directory tree from SourceDirectory.
  • Context.TestException: Access to the exception if the current test has failed. See Test Failure.

public class ContextSample  :
    XunitContextBase
{
    [Fact]
    public void Usage()
    {
        Context.WriteLine("Some message");

        var currentLogMessages = Context.LogMessages;

        var testOutputHelper = Context.TestOutput;

        var currentTest = Context.Test;

        var sourceFile = Context.SourceFile;

        var sourceDirectory = Context.SourceDirectory;

        var solutionDirectory = Context.SolutionDirectory;

        var currentTestException = Context.TestException;
    }

    public ContextSample(ITestOutputHelper output) :
        base(output)
    {
    }
}

snippet source | anchor

Some members are pushed down to the be accessible directly from XunitContextBase:

public class ContextPushedDownSample  :
    XunitContextBase
{
    [Fact]
    public void Usage()
    {
        WriteLine("Some message");

        var currentLogMessages = Logs;

        var testOutputHelper = Output;

        var sourceFile = SourceFile;

        var sourceDirectory = SourceDirectory;

        var solutionDirectory = SolutionDirectory;

        var currentTestException = TestException;
    }

    public ContextPushedDownSample(ITestOutputHelper output) :
        base(output)
    {
    }
}

snippet source | anchor

Context can accessed via a static API:

public class ContextStaticSample :
    XunitContextBase
{
    [Fact]
    public void StaticUsage()
    {
        XunitContext.Context.WriteLine("Some message");

        var currentLogMessages = XunitContext.Context.LogMessages;

        var testOutputHelper = XunitContext.Context.TestOutput;

        var currentTest = XunitContext.Context.Test;

        var sourceFile = XunitContext.Context.SourceFile;

        var sourceDirectory = XunitContext.Context.SourceDirectory;

        var solutionDirectory = XunitContext.Context.SolutionDirectory;

        var currentTestException = XunitContext.Context.TestException;
    }

    public ContextStaticSample(ITestOutputHelper output) :
        base(output)
    {
    }
}

snippet source | anchor

Current Test

There is currently no API in xUnit to retrieve information on the current test. See issues #1359, #416, and #398.

To work around this, this project exposes the current instance of ITest via reflection.

Usage:

public class CurrentTestSample :
    XunitContextBase
{
    [Fact]
    public void Usage()
    {
        var currentTest = Context.Test;
        // DisplayName will be 'TestNameSample.Usage'
        var displayName = currentTest.DisplayName;
    }

    [Fact]
    public void StaticUsage()
    {
        var currentTest = XunitContext.Context.Test;
        // DisplayName will be 'TestNameSample.StaticUsage'
        var displayName = currentTest.DisplayName;
    }

    public CurrentTestSample(ITestOutputHelper output) :
        base(output)
    {
    }
}

snippet source | anchor

Implementation:

using Xunit.Sdk;

namespace Xunit;

public partial class Context
{
    ITest? test;

    static FieldInfo? cachedTestMember;

    public ITest Test
    {
        get
        {
            InitTest();

            return test!;
        }
    }

    MethodInfo? methodInfo;
    public MethodInfo MethodInfo
    {
        get
        {
            InitTest();
            return methodInfo!;
        }
    }

    Type? testType;
    public Type TestType
    {
        get
        {
            InitTest();
            return testType!;
        }
    }

    void InitTest()
    {
        if (test != null)
        {
            return;
        }
        test = (ITest) GetTestMethod().GetValue(TestOutput);
        var method = (ReflectionMethodInfo) test.TestCase.TestMethod.Method;
        var type = (ReflectionTypeInfo) test.TestCase.TestMethod.TestClass.Class;
        methodInfo = method.MethodInfo;
        testType = type.Type;
    }

    public static string MissingTestOutput = "ITestOutputHelper has not been set. It is possible that the call to `XunitContext.Register()` is missing, or the current test does not inherit from `XunitContextBase`.";

    FieldInfo GetTestMethod()
    {
        if (TestOutput == null)
        {
            throw new(MissingTestOutput);
        }

        if (cachedTestMember != null)
        {
            return cachedTestMember;
        }

        var testOutputType = TestOutput.GetType();
        cachedTestMember = testOutputType.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic);
        if (cachedTestMember == null)
        {
            throw new($"Unable to find 'test' field on {testOutputType.FullName}");
        }

        return cachedTestMember;
    }
}

snippet source | anchor

Test Failure

When a test fails it is expressed as an exception. The exception can be viewed by enabling exception capture, and then accessing Context.TestException. The TestException will be null if the test has passed.

One common case is to perform some logic, based on the existence of the exception, in the Dispose of a test.

public static class GlobalSetup
{
    [ModuleInitializer]
    public static void Setup() =>
        XunitContext.EnableExceptionCapture();
}

[Trait("Category", "Integration")]
public class TestExceptionSample :
    XunitContextBase
{
    [Fact]
    public void Usage() =>
        //This tests will fail
        Assert.False(true);

    public TestExceptionSample(ITestOutputHelper output) :
        base(output)
    {
    }

    public override void Dispose()
    {
        var theExceptionThrownByTest = Context.TestException;
        var testDisplayName = Context.Test.DisplayName;
        var testCase = Context.Test.TestCase;
        base.Dispose();
    }
}

snippet source | anchor

Base Class

When creating a custom base class for other tests, it is necessary to pass through the source file path to XunitContextBase via the constructor.

public class CustomBase :
    XunitContextBase
{
    public CustomBase(
        ITestOutputHelper testOutput,
        [CallerFilePath] string sourceFile = "") :
        base(testOutput, sourceFile)
    {
    }
}

snippet source | anchor

Parameters

Provided the parameters passed to the current test when using a [Theory].

Use cases:

Usage:

public class ParametersSample :
    XunitContextBase
{
    [Theory]
    [MemberData(nameof(GetData))]
    public void Usage(string arg)
    {
        var parameter = Context.Parameters.Single();
        var parameterInfo = parameter.Info;
        Assert.Equal("arg", parameterInfo.Name);
        Assert.Equal(arg, parameter.Value);
    }

    public static IEnumerable<object[]> GetData()
    {
        yield return new object[] {"Value1"};
        yield return new object[] {"Value2"};
    }

    public ParametersSample(ITestOutputHelper output) :
        base(output)
    {
    }
}

snippet source | anchor

Implementation:

static List<Parameter> GetParameters(ITestCase testCase) =>
    GetParameters(testCase, testCase.TestMethodArguments);

static List<Parameter> GetParameters(ITestCase testCase, object[] arguments)
{
    var method = testCase.TestMethod;
    var infos = method.Method.GetParameters().ToList();
    if (arguments == null || !arguments.Any())
    {
        if (infos.Count == 0)
        {
            return empty;
        }

        throw NewNoArgumentsDetectedException();
    }

    List<Parameter> items = new();

    for (var index = 0; index < infos.Count; index++)
    {
        items.Add(new(infos[index], arguments[index]));
    }

    return items;
}

snippet source | anchor

Complex parameters

Only core types (string, int, DateTime etc) can use the above automated approach. If a complex type is used the following exception will be thrown

No arguments detected for method with parameters. This is most likely caused by using a parameter that Xunit cannot serialize. Instead pass in a simple type as a parameter and construct the complex object inside the test. Alternatively; override the current parameters using UseParameters() via the current test base class, or via XunitContext.Current.UseParameters().

To use complex types override the parameter resolution using XunitContextBase.UseParameters:

public class ComplexParameterSample :
    XunitContextBase
{
    [Theory]
    [MemberData(nameof(GetData))]
    public void UseComplexMemberData(ComplexClass arg)
    {
        UseParameters(arg);
        var parameter = Context.Parameters.Single();
        var parameterInfo = parameter.Info;
        Assert.Equal("arg", parameterInfo.Name);
        Assert.Equal(arg, parameter.Value);
    }

    public static IEnumerable<object[]> GetData()
    {
        yield return new object[] {new ComplexClass("Value1")};
        yield return new object[] {new ComplexClass("Value2")};
    }

    public ComplexParameterSample(ITestOutputHelper output) :
        base(output)
    {
    }

    public class ComplexClass
    {
        public string Value { get; }

        public ComplexClass(string value) =>
            Value = value;
    }
}

snippet source | anchor

UniqueTestName

Provided a string that uniquely identifies a test case.

Usage:

public class UniqueTestNameSample :
    XunitContextBase
{
    [Fact]
    public void Usage()
    {
        var testName = Context.UniqueTestName;

        Context.WriteLine(testName);
    }

    public UniqueTestNameSample(ITestOutputHelper output) :
        base(output)
    {
    }
}

snippet source | anchor

Implementation:

string GetUniqueTestName(ITestCase testCase)
{
    var method = testCase.TestMethod;
    var name = $"{method.TestClass.Class.ClassName()}.{method.Method.Name}";
    if (!Parameters.Any())
    {
        return name;
    }

    StringBuilder builder = new($"{name}_");
    foreach (var parameter in Parameters)
    {
        builder.Append($"{parameter.Info.Name}=");
        builder.Append(string.Join(",", SplitParams(parameter.Value)));
        builder.Append('_');
    }

    builder.Length -= 1;

    return builder.ToString();
}

static IEnumerable<string> SplitParams(object? parameter)
{
    if (parameter == null)
    {
        yield return "null";
        yield break;
    }

    if (parameter is string stringValue)
    {
        yield return stringValue;
        yield break;
    }

    if (parameter is IEnumerable enumerable)
    {
        foreach (var item in enumerable)
        {
            foreach (var sub in SplitParams(item))
            {
                yield return sub;
            }
        }

        yield break;
    }

    yield return parameter.ToString();
}

snippet source | anchor

Global Setup

Xunit has no way to run code once before any tests executing. So use one of the following:

Icon

Wolverine designed by Mike Rowe from The Noun Project.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].