XspecT 17.1.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package XspecT --version 17.1.3                
NuGet\Install-Package XspecT -Version 17.1.3                
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="17.1.3" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add XspecT --version 17.1.3                
#r "nuget: XspecT, 17.1.3"                
#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=17.1.3

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

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, although 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.

Class fixtures

XspecT now support the Xunit feature of sharing setup between tests in a common class fixture. A class fixture is created in the same way as a test class, by inheriting Spec and providing setup. The only difference between a class fixture implemented with XspecT and a test class implemented with XspecT is that class fixtures don't have test methods or assertions.

When using a class fixture and having more that one test method, no setup should be put in the constructor, since the constructor is run once for each test method and provide the setup to the shared class fixture (i.e would add the same setup multiple times and second time after the test pipeline was executed, which is not allowed)

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
17.1.5 126 11/10/2024
17.1.4 74 11/10/2024
17.1.3 73 11/3/2024
17.1.2 72 11/2/2024
17.1.1 155 10/29/2024
17.1.0 82 10/28/2024
17.0.3 117 10/14/2024
17.0.2 78 10/14/2024
17.0.1 78 10/14/2024
17.0.0 87 10/12/2024
17.0.0-pre.3 45 10/8/2024
17.0.0-pre.2 41 10/6/2024
17.0.0-pre.1 43 10/6/2024
16.4.1 93 10/5/2024
16.4.0 113 10/4/2024
16.3.1 84 9/22/2024
16.3.0 89 9/22/2024
16.2.1 215 9/14/2024
16.2.0 94 9/14/2024
16.1.5 89 9/7/2024
16.1.4 96 9/7/2024
16.1.3 162 9/7/2024
16.1.2 87 9/6/2024
16.1.1 100 9/3/2024
16.1.0 97 9/2/2024
16.0.4 285 8/18/2024
16.0.4-preview 110 8/18/2024
16.0.3-preview 105 8/18/2024
16.0.2-preview 109 8/17/2024
16.0.1-preview 110 8/17/2024
16.0.0-preview 105 8/16/2024
15.7.0 176 8/7/2024
15.6.2 78 7/29/2024
15.6.1 179 7/14/2024
15.6.0 89 7/13/2024
15.5.4 149 7/7/2024
15.5.3 103 7/7/2024
15.5.2 102 7/7/2024
15.5.1 100 7/2/2024
15.5.0 105 6/30/2024
15.4.1 101 6/29/2024
15.4.0 119 6/24/2024
15.3.2 95 6/24/2024
15.3.1 113 6/23/2024
15.3.0 115 6/23/2024
15.2.1 123 6/20/2024
15.2.0 118 6/19/2024
15.1.3-preview 98 6/19/2024
15.1.2 111 6/18/2024
15.1.1 127 6/17/2024
15.1.0 126 6/16/2024
15.0.1 109 6/15/2024
15.0.0 108 6/9/2024
14.2.1 103 6/6/2024
14.2.0 100 6/6/2024
14.1.0 113 5/13/2024
14.0.0 102 5/9/2024
13.3.2 228 4/7/2024
13.3.1 118 1/31/2024
13.3.0 333 1/20/2024
13.2.3 123 1/15/2024
13.2.2 118 1/13/2024
13.2.1 125 1/2/2024
13.2.0 171 1/2/2024
13.1.2 131 12/19/2023
13.1.1 165 12/19/2023
13.1.0 136 12/18/2023
13.0.1 109 12/17/2023
13.0.0 120 12/17/2023
12.2.2 121 12/16/2023
12.2.1 115 12/16/2023
12.2.0 119 12/16/2023
12.1.1 125 12/16/2023
12.1.0 149 12/3/2023
12.0.0 137 12/2/2023
11.0.4 141 11/28/2023
11.0.3 237 11/19/2023
11.0.2 132 11/19/2023
11.0.1 137 11/18/2023
11.0.0 134 11/18/2023
10.0.2 144 11/18/2023
10.0.1 130 11/15/2023
10.0.0 138 11/12/2023
9.3.2 124 11/12/2023
9.3.1 124 11/12/2023
9.3.0 133 11/11/2023
9.2.1 136 11/11/2023
9.2.0 137 11/5/2023
9.1.1 143 10/29/2023
9.1.0 146 10/28/2023
9.0.0 155 10/28/2023
8.5.1 149 10/27/2023
8.5.0 149 10/26/2023
8.4.0 163 10/22/2023
8.3.1 160 10/22/2023
8.3.0 153 10/22/2023
8.2.1 149 10/22/2023
8.2.0 139 10/21/2023
8.1.2 146 10/21/2023
8.1.1 142 10/20/2023
8.1.0 131 10/20/2023
8.0.1 154 10/18/2023
8.0.0 136 10/16/2023
7.2.0 144 10/16/2023
7.1.1 139 10/12/2023
7.1.0 164 10/8/2023
7.0.1 136 10/1/2023
7.0.0 133 10/1/2023
6.4.0 154 9/30/2023
6.3.2 123 9/30/2023
6.3.1 147 9/30/2023
6.3.0 124 9/25/2023
6.2.4 146 9/15/2023
6.2.3 140 9/15/2023
6.2.2 128 9/15/2023
6.2.1 156 9/15/2023
6.2.0 143 9/14/2023
6.1.3 154 9/13/2023
6.1.2 167 9/12/2023
6.1.1 148 9/12/2023
6.1.0 175 9/10/2023
6.0.0 148 9/9/2023
5.5.0 159 9/8/2023
5.4.3 146 9/7/2023
5.4.2 167 9/5/2023
5.4.1 137 9/3/2023
5.4.0 236 8/28/2023
5.3.1 171 8/28/2023
5.3.0 149 8/27/2023
5.2.0 167 8/27/2023
5.1.1 165 8/26/2023
5.1.0 161 8/26/2023
5.0.0 170 8/26/2023
4.5.2 148 8/26/2023
4.5.1 153 8/26/2023
4.5.0 145 8/26/2023
4.4.7 157 8/22/2023
4.4.6 141 8/22/2023
4.4.5 137 8/21/2023
4.4.4 169 8/20/2023
4.4.3 162 8/16/2023
4.4.2 173 8/15/2023
4.4.1 170 8/15/2023
4.4.0 188 8/15/2023
4.3.1 168 8/14/2023
4.3.0 188 8/14/2023
4.2.0 178 8/14/2023
4.1.1 169 8/11/2023
4.1.0 166 8/9/2023
4.0.0 182 8/8/2023
3.3.2 165 8/7/2023
3.3.1 162 8/6/2023
3.3.0 168 8/6/2023
3.2.1 163 8/6/2023
3.2.0 201 8/6/2023
3.1.0 195 8/5/2023
3.0.0 184 8/2/2023
2.4.1 180 8/1/2023
2.4.0 168 8/1/2023
2.3.1 175 7/30/2023
2.3.0 166 7/30/2023
2.2.3 167 7/29/2023
2.2.2 177 7/28/2023
2.2.1 173 7/24/2023
2.2.0 182 7/24/2023
2.1.1 182 7/23/2023
2.1.0 179 7/23/2023
2.0.1 181 7/21/2023
2.0.0 189 7/21/2023
1.1.0 199 7/20/2023
1.0.0 163 7/20/2023

Do not throw NullRefException when providing null as default array