DEV Community

Shantanu
Shantanu

Posted on • Edited on

xUnit - Run async code specific to test before/after test

xUnit allows you to run code before each test using the BeforeAfterTestAttribute.

But, you cannot run asynchronous code using this attribute. This is needed in many situations.

To solve this problem, I have created a custom abstract xUnit attribute BeforeAfterAsyncTestAttribute,

inheriting from BeforeAfterTestAttribute, that allows you to run asynchronous code before each test or group of tests.

Inherit and create a class for each test.

public class MyBeforeAfterAsyncTestAttribute : BeforeAfterAsyncTestAttribute
{
    public MyBeforeAfterAsyncTestAttribute(Type specificAttributeType, string stamp) : base(specificAttributeType, stamp)
    {
    }

    public MyBeforeAfterAsyncTestAttribute(Type specificAttribute, Type returnFunctionClassType,
                                                string returnFunctionName, string stamp)
                                                : base(specificAttribute, returnFunctionClassType, returnFunctionName, stamp)
    {
    }
}
Enter fullscreen mode Exit fullscreen mode

These are interfaces your specific Test attribute has to implement.

public interface IRunBeforeAsync : IRunAsync
{
    Action? RunBefore { get; }
}

public interface IRunAfterAsync : IRunAsync
{
    Action? RunAfter { get; }
}

public interface IRunBeforeAsyncWithReturn : IRunBeforeAsync
{
    object? ReturnValue { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

In the interface implementation, specific to each test, put your code specific to the Test in the Run Action, as shown below.

Here you put your asynchronous code.

Examples

public class LoadAIModel : IRunBeforeAsync, IRunAfterAsync
{
    public Action RunBefore => async () =>
    {
        // Arrange
        // Path to load model
        string modelPath = Path.Combine(Environment.CurrentDirectory, "SampleWebsite-AI-Model.zip");

        await PredictionEngine.LoadModelAsync(modelPath);
    };

    public Action RunAfter => async () =>
    {
        // Clean up resources after the test, if necessary
        await PredictionEngine.UnloadModelAsync();
    };
}

public class SetAIModelPath : IRunBeforeAsync, IRunAfterAsync
{
    public Action RunBefore => async () =>
    {
        // Arrange
        // Path to load model
        string modelPath = Path.Combine(Environment.CurrentDirectory, "SampleWebsite-AI-Model.zip");
        // Provide the path to the AI model
        PredictionEngine.AIModelLoadFilePath = modelPath;
    };

    public Action RunAfter => async () =>
    {
        // Clean up resources after the test, if necessary
        await PredictionEngine.UnloadModelAsync();
    };
}
Enter fullscreen mode Exit fullscreen mode

Then, you can decorate those specific tests.
Provide a Guid (as a string) as a parameter. This Guid should be unique to the test.

    [MyBeforeAfterAsyncTest(typeof(LoadAIModel), "5bb02c70-01d1-4987-8a6e-ab7fc8b1dcc4")]
    [Theory]
    [InlineData("What are the requisites for carbon credits?", Scheme.ACCU)]
    [InlineData("How do I calculate net emissions?", Scheme.SafeguardMechanism)]
    [InlineData("What is the colour of a rose?", Scheme.None)]
    public async Task Load_Predict(string userInput, Scheme expectedResult)
    {
        var input = new ModelInput { Feature = userInput };

        // Act
        var prediction = await PredictionEngine.PredictAsync(input);

        // Assert
        Assert.NotNull(prediction);
        Assert.Equal(expectedResult, (Scheme)prediction.PredictedLabel);
    }

    [MyBeforeAfterAsyncTest(typeof(SetAIModelPath), "d54e2920-ad42-4acc-a6e2-37aad8e9ac3f")]
    [Theory]
    [InlineData("What are the requisites for carbon credits?", Scheme.ACCU)]
    [InlineData("How do I calculate net emissions?", Scheme.SafeguardMechanism)]
    [InlineData("What is the colour of a rose?", Scheme.None)]
    public async Task AutoLoad_Predict(string userInput, Scheme expectedResult)
    {
        var input = new ModelInput { Feature = userInput };

        // Act
        var prediction = await PredictionEngine.PredictAsync(input);

        // Assert
        Assert.NotNull(prediction);
        Assert.Equal(expectedResult, (Scheme)prediction.PredictedLabel);
    }
Enter fullscreen mode Exit fullscreen mode

Run all the tests in the class.

Your specific code will run ONLY ONCE before each group of Theory Tests.

So, for example, your specific code in LoadAIModel will run asynchronously only once for the 3 Tests in the Theory group.

You can browse the project's source code repository on GitHub:

GitHub logo VeritasSoftware / xUnit-Addons

Useful addons for xUnit

xUnit Addons

Run asynchronous code specific to test, before/after test

xUnit allows you to run code before each test using the BeforeAfterTestAttribute.

But, you cannot run asynchronous code using this attribute. This is needed in many situations.

To solve this problem, I have created a custom abstract xUnit attribute BeforeAfterAsyncTestAttribute,

inheriting from BeforeAfterTestAttribute, that allows you to run asynchronous code before each test or group of tests.

First, inherit from this attribute and create an attribute for each test.

You can re-use the attribute in multiples tests too. Just pass in a different Guid in the stamp parameter.

public class MyBeforeAfterAsyncTestAttribute : BeforeAfterAsyncTestAttribute
{
    public MyBeforeAfterAsyncTestAttribute(Type specificAttributeType, string stamp) : base(specificAttributeType, stamp)
    {
    }
    public MyBeforeAfterAsyncTestAttribute(Type specificAttribute, Type returnFunctionClassType,
                                                string returnFunctionName, string stamp)
                                                : base(specificAttribute, returnFunctionClassType, returnFunctionName, stamp
Enter fullscreen mode Exit fullscreen mode

Top comments (0)