XspecT 16.3.1

dotnet add package XspecT --version 16.3.1                
NuGet\Install-Package XspecT -Version 16.3.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="XspecT" Version="16.3.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add XspecT --version 16.3.1                
#r "nuget: XspecT, 16.3.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install XspecT as a Cake Addin
#addin nuget:?package=XspecT&version=16.3.1

// Install XspecT as a Cake Tool
#tool nuget:?package=XspecT&version=16.3.1                

XspecT: A fluent unit testing framework

Framework for writing and running automated tests in .Net in a fluent style, based on the popular "Given-When-Then" pattern, built upon XUnit, Moq, AutoMock, AutoFixture and FluentAssertions.

Whether you are beginner or expert in unit-testing, this framework will help you to write more descriptive, concise and maintainable tests.

Usage

It is assumed that you are already familiar with Xunit and Moq, or similar test- and mocking frameworks. XspecT includes a fluent assertion framework called XspecT.Assert, which is built upon FluentAssertions, but with a less worthy syntax, based on the verbs Is, Has and Does instead of Should.

This is an example of a complete test class (specification) with one test method (requirement):

using XspecT;
using XspecT.Assert;
using static App.Calculator;

namespace App.Test;

public class CalculatorSpec : Spec<int>
{
    [Fact] public void WhenAdd_1_and_2_ThenSumIs_3() => When(_ => Add(1, 2)).Then().Result.Is(3);
}

To write a test with the XspecT framework, such as the one above, you first need to subclass Spec. A test execution contains three different phases: arrange, act and assert.

We will begin with the first stage:

There are a number of different methods in Spec that can be called to arrange the test pipeline. These are:

  • Given (for arrangement)
  • After (for setup)
  • Before (for teardown)

These methods can be called directly on the base class, or chained on each other (most tests can be expressed as one-liners, althoug it may not be recommended for readability).

In addition there are a number of methods to refer to test-data that can either be provided explicitly or auto-generated (with or without constraints). Up to 5 different values can be provided of any given type, as well as collections of up to five elements of any type. The methods for referring to/creating test data are named A, An, The, AFirst, TheFirst, ASecond, TheSecond and so on for single values and Some, Many, Zero, One, Two, Three, Four and Five for collections

The act stage is specified by calling When with the lambda that will be executed. The lambda takes the subject-under-test as argument and should call the method-under-test. The subject-under-test will be automatically generated based on the arrangement (unless static or explicitly provided). It doesn't matter in which order Given or When is called, and they may also be chained in any order.

Finally to specify the assert stage, call Then or Result, followed by any assertions you want to make. It is not until one of these two methods are called that the test-pipeline is executed and the test result provided. This allows the XspecT framework to arrange the test-pipeline in the natural order, regardless of in what order those arrangements were supplied in the implementation of the test. This means that in most cases you don't have to worry about the order in which the steps of the test is specified (as long as assert comes after arrange and act). In more complex tests, different arrangements may depend on each other, which makes the order in which they are supplied significant, but it is recommended to keep unit tests as simple, targeted and readable as possible.

Should a test fail, this can be due to either invalid setup or that the test condition (assertion) is not satisfied. In the first case a SetupFailed exception is thrown detailing the error in the setup (this could be for instance if Given is called after Then or When is called multiple times) In the second case, you are in the red zone of the red-green-refactor cycle and need to either fix the test or the implementation being tested. To help with this, the built in assertion framework supply not only the details of the error, but also a complete description of the test (the specification, which is auto-generated from the test implementation), so that you can more easily se what behavior the test actually expects, than from reading the test implementation alone.

After this introduction, we should be ready to look at more examples.

Test a static method with [Theory]

If you are used to writing one test class per production class and use Theory for test input, you can use a similar style with XspecT. First you create your test-class overriding Spec<[ReturnType]> with the expected return type as generic argument. Then create a test-method, attributed with Theory and InlineData, called When[Something]. This method call When to setup the test pipeline with test data and the method to test. Finally verify the result by calling Then().Result (or only Result) on the returned pipeline and check the result with Is.

Example:

using XspecT.Verification;
using XspecT.Fixture;

using static App.Calculator;

namespace App.Test;

public class CalculatorSpec : Spec<int>
{
    [Theory]
    [InlineData(1, 1, 2)]
    [InlineData(3, 4, 7)]
    public void GivenTwoNumbers_WhenAdd_ReturnSum(int term1, int term2, int sum)
        => When(_ => Add(term1, term2)).Then().Result.Is(sum);
}

For more complex and realistic scenarios, it is recommended to create tests in a separate project from the production code, named [MyProject].Spec or [MyProject].Test. The test-project should mimic the production project's folder structure, but in addition have one folder for each class to test, named as the class. Within that folder, create one test-class per method to test, named When[Something]. Within the when-class, which should be abstract, create a nested public subclass for each condition, called Given[Something], in which one test method is defined for each logical assert.

Example:

namespace MyProject.Test.Validator;

public abstract class WhenVerifyAreEqual : Spec
{
    protected WhenVerifyAreEqual() 
        => When(_ => MyProject.Validator.VerifyAreEqual(An<int>(), ASecond<int>()));

    public class Given_1_And_2 : WhenVerifyAreEqual
    {
        [Fact] public void ThenThrows_NotEqual() => Given(1, 2).Then().Throws<NotEqual>();
    }

    public class Given_2_And_2 : WhenVerifyAreEqual
    {
        [Fact] public void ThenDoNotThrow() => Given(2, 2).Then().DoesNotThrow();
    }
}

Note that when no return value is asserted, we can use the non-generic base class Spec.

Throws and DoesNotThrow can be used to verify exceptions.

Test a class instance with dependencies

To test an instance method [MyClass].[MyMethod], create an abstract class named When[MyMethod] inheriting XspecT.Spec<[MyClass], [TheResult]>. The subject under test will be created automatically with mocks and default values by AutoMock. Subject-under-test is available as the single input parameter to the lambda that is provided to the method When. You can supply or modify you own constructor arguments by calling Given or Given().Using. You can even provide the instance to test by using any of those two methods.

To mock behavior of any dependency call Given<[TheService]>().That(_ => _.[TheMethod](...)).Returns/Throws(...).

To verify a call to a mocked dependency, call Then<[TheService]>([SomeLambdaExpression]).

Both mocking and verification of behavior is based on Moq framework.

Example:

namespace MyProject.Spec.ShoppingService;

public class WhenPlaceOrder : Spec<MyProject.ShoppingService>
{
    protected WhenPlaceOrder() 
        => When(_ => _.PlaceOrder(An<int>()))
        .Given<ICartRepository>().That(_ => _.GetCart(The<int>()))
        .Returns(() => A<Cart>(_ => _.Id = The<int>()));

    [Fact] public void ThenOrderIsCreated() => Then<IOrderService>(_ => _.CreateOrder(The<Cart>()));

    [Fact] public void ThenLogsOrderCreated()
        => Then<ILogger>(_ => _.Information($"OrderCreated from Cart {The<int>()}"));
}

Sync vs. Async

Weather your method-under-test or mocked methods are sync or async, the tests are specified in the exact same way. The XspecT framework will call async methods synchronously, so that the test does not have to await any calls, but can always be treated as if they are testing synchronous methods. However in some cases you have to use async and await keywords in the lambdas you provide to the test-pipeline to deal with async scenarios.

This primer should be enough to get you started. More documentation is available as code comments. More examples and features can also be found as Unit tests in the source code, which is available on GitHub.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
16.3.1 55 9/22/2024
16.3.0 60 9/22/2024
16.2.1 83 9/14/2024
16.2.0 66 9/14/2024
16.1.5 61 9/7/2024
16.1.4 65 9/7/2024
16.1.3 99 9/7/2024
16.1.2 60 9/6/2024
16.1.1 73 9/3/2024
16.1.0 71 9/2/2024
16.0.4 176 8/18/2024
16.0.4-preview 88 8/18/2024
16.0.3-preview 82 8/18/2024
16.0.2-preview 85 8/17/2024
16.0.1-preview 87 8/17/2024
16.0.0-preview 96 8/16/2024
15.7.0 132 8/7/2024
15.6.2 56 7/29/2024
15.6.1 156 7/14/2024
15.6.0 67 7/13/2024
15.5.4 130 7/7/2024
15.5.3 79 7/7/2024
15.5.2 81 7/7/2024
15.5.1 83 7/2/2024
15.5.0 89 6/30/2024
15.4.1 86 6/29/2024
15.4.0 95 6/24/2024
15.3.2 75 6/24/2024
15.3.1 90 6/23/2024
15.3.0 87 6/23/2024
15.2.1 87 6/20/2024
15.2.0 100 6/19/2024
15.1.3-preview 78 6/19/2024
15.1.2 93 6/18/2024
15.1.1 105 6/17/2024
15.1.0 104 6/16/2024
15.0.1 92 6/15/2024
15.0.0 92 6/9/2024
14.2.1 82 6/6/2024
14.2.0 81 6/6/2024
14.1.0 95 5/13/2024
14.0.0 82 5/9/2024
13.3.2 202 4/7/2024
13.3.1 101 1/31/2024
13.3.0 310 1/20/2024
13.2.3 104 1/15/2024
13.2.2 101 1/13/2024
13.2.1 103 1/2/2024
13.2.0 154 1/2/2024
13.1.2 103 12/19/2023
13.1.1 144 12/19/2023
13.1.0 112 12/18/2023
13.0.1 105 12/17/2023
13.0.0 103 12/17/2023
12.2.2 104 12/16/2023
12.2.1 109 12/16/2023
12.2.0 98 12/16/2023
12.1.1 104 12/16/2023
12.1.0 129 12/3/2023
12.0.0 117 12/2/2023
11.0.4 114 11/28/2023
11.0.3 216 11/19/2023
11.0.2 114 11/19/2023
11.0.1 117 11/18/2023
11.0.0 111 11/18/2023
10.0.2 121 11/18/2023
10.0.1 110 11/15/2023
10.0.0 119 11/12/2023
9.3.2 105 11/12/2023
9.3.1 106 11/12/2023
9.3.0 112 11/11/2023
9.2.1 116 11/11/2023
9.2.0 117 11/5/2023
9.1.1 123 10/29/2023
9.1.0 125 10/28/2023
9.0.0 134 10/28/2023
8.5.1 124 10/27/2023
8.5.0 127 10/26/2023
8.4.0 141 10/22/2023
8.3.1 139 10/22/2023
8.3.0 132 10/22/2023
8.2.1 128 10/22/2023
8.2.0 122 10/21/2023
8.1.2 123 10/21/2023
8.1.1 121 10/20/2023
8.1.0 109 10/20/2023
8.0.1 129 10/18/2023
8.0.0 112 10/16/2023
7.2.0 110 10/16/2023
7.1.1 118 10/12/2023
7.1.0 141 10/8/2023
7.0.1 117 10/1/2023
7.0.0 115 10/1/2023
6.4.0 132 9/30/2023
6.3.2 116 9/30/2023
6.3.1 124 9/30/2023
6.3.0 118 9/25/2023
6.2.4 128 9/15/2023
6.2.3 118 9/15/2023
6.2.2 107 9/15/2023
6.2.1 132 9/15/2023
6.2.0 136 9/14/2023
6.1.3 131 9/13/2023
6.1.2 131 9/12/2023
6.1.1 129 9/12/2023
6.1.0 152 9/10/2023
6.0.0 126 9/9/2023
5.5.0 140 9/8/2023
5.4.3 123 9/7/2023
5.4.2 144 9/5/2023
5.4.1 131 9/3/2023
5.4.0 192 8/28/2023
5.3.1 150 8/28/2023
5.3.0 122 8/27/2023
5.2.0 132 8/27/2023
5.1.1 141 8/26/2023
5.1.0 140 8/26/2023
5.0.0 149 8/26/2023
4.5.2 127 8/26/2023
4.5.1 129 8/26/2023
4.5.0 136 8/26/2023
4.4.7 136 8/22/2023
4.4.6 135 8/22/2023
4.4.5 116 8/21/2023
4.4.4 146 8/20/2023
4.4.3 141 8/16/2023
4.4.2 151 8/15/2023
4.4.1 145 8/15/2023
4.4.0 163 8/15/2023
4.3.1 149 8/14/2023
4.3.0 144 8/14/2023
4.2.0 155 8/14/2023
4.1.1 147 8/11/2023
4.1.0 146 8/9/2023
4.0.0 154 8/8/2023
3.3.2 127 8/7/2023
3.3.1 140 8/6/2023
3.3.0 141 8/6/2023
3.2.1 144 8/6/2023
3.2.0 153 8/6/2023
3.1.0 170 8/5/2023
3.0.0 162 8/2/2023
2.4.1 160 8/1/2023
2.4.0 146 8/1/2023
2.3.1 152 7/30/2023
2.3.0 143 7/30/2023
2.2.3 148 7/29/2023
2.2.2 159 7/28/2023
2.2.1 153 7/24/2023
2.2.0 162 7/24/2023
2.1.1 161 7/23/2023
2.1.0 160 7/23/2023
2.0.1 160 7/21/2023
2.0.0 170 7/21/2023
1.1.0 178 7/20/2023
1.0.0 144 7/20/2023

Apply teardown on Dispose instead of last in the test pipeline