XspecT 19.0.1

dotnet add package XspecT --version 19.0.1
                    
NuGet\Install-Package XspecT -Version 19.0.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="19.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="XspecT" Version="19.0.1" />
                    
Directory.Packages.props
<PackageReference Include="XspecT" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add XspecT --version 19.0.1
                    
#r "nuget: XspecT, 19.0.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.
#:package XspecT@19.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=XspecT&version=19.0.1
                    
Install as a Cake Addin
#tool nuget:?package=XspecT&version=19.0.1
                    
Install as a Cake Tool

XspecT: A fluent unit testing framework

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

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

Introduction

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 similar to 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:

Arrange

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)

The motivation behind the naming of After and before Before is that When is executed before Before and after After. These methods can be called directly on the base class, or chained on each other (most tests can be expressed as one-liners).

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. Calling any of these methods multiple times yield the same value every time within the same test. A, An, The, AFirst and TheFirst are synonyms,yielding the first auto-generated value of the given type. Any or Another give a new value that cannot be referenced again.

Act

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, Before, After or When is called, and they may be chained in any order.

Assert

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 under test. 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.

Apart from verifying the result (return value) of the method, you can also verify that it throws the expected exceotion with Then().Throws.

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);
}

Test a class instance with dependencies

To test an instance method [MyClass].[MyMethod], create an abstract class named When[MyMethod] inheriting XspecT.Spec<[MyClass], [ReturnType]>. 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>()}"));
}

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.

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.

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).

Tags

Tags can be used as a complement to AFirst, ASecond, etc. to refer to values in a test setup in a more expressive way. They are particularly useful when having several values of the same type. A tag is a simple object of the generic type Tag<TValue>. Each tag instance represents a unique value. Start by creating one tag for each 'tagged' value you want to use in the test setup.

Example:

protected static Tag<string> name = new();
protected static Tag<int> age = new(), shoeSize= new();

Set and reference tagged values

Then you can use the tags to set or reference values in a test-pipeline.

Example:

Given(surname).Is("Anton").And(lastname).Is("Jonson")
.When(_ => _.CreateUser(The(surname), The(lastname)))
.Then().Result.FullName.Is("Anton Jonson");

Use tagged values as default or for auto-generation

You can also specify that the value associated with a tag should be the default value of that type, or used when auto-generating subject-under-test.

Example:

Given().Default(name).And().Using(age);

Assertions

A deep dive in the fluent assertion framework.

Fluent assertions

Assertions are made directly on the value to be verified. Several assertions can be chained together: Every assertion returns a continuation (unless it fails and throws a XunitException). The continuation is context-aware and allows different assertions depending on what was asserted previously.

3.Is().GreaterThan(2).And.LessThan(4);

3.Is().Either.GreaterThan(4).Or.LessThan(4);
Not

Any assertion can be negated by placing Not before

3.Is().Not().GreaterThan(4);

Values

Values of any type can be verified with any of the two extension methods Is and Has

Is
  • Equal:
    Result.Is(3)
    Result.Is().EqualTo(3)
  • Equivalent: (for objects)
    Result.Is().Like(new MyObject {Id = 3})
    Result.Is().EquivalentTo(new MyObject {Id = 3})
  • Not equal:
    Result.Is().Not(3)
  • Null:
    Result.Is().Null()
  • Greater than:
    3.Is().GreaterThan(2)
  • Less than:
    3.Is().LessThan(2)
  • Aproximally equal with tolerance:
    Result.Is().Around(3, 0.1)
  • Even: (true if number is divisable by 2)
    Result.Is().Even()
  • OneOf:
    Result.Is().OneOf(Three<int>())
  • True: (for booleans)
    Result.Is().True()
  • False: (for booleans)
    Result.Is().False()
Has
  • Verify that the result has a given condition:
    Result.Has(_ => _.Id == 3)
  • Verify that the result has the given type:
    Result.Has().Type<MyModel>()

Strings

Is
  • Like
    " ABC ".Is().Like("abc")
  • EquivalentTo
    " ABC ".Is().EquivalentTo("abc")
  • Empty
    "".Is().Empty()
  • NullOrEmpty
    ((string)null).Is().NullOrEmpty()
  • NullOrWhitespace
    " ".Is().NullOrWhitespace()
Does
  • Contain
    "ABC".Does().Contain("AB")
  • StartWith
    "ABC".Does().StartWith("AB")
  • EndWith
    "ABC".Does().EndWith("BC")

Time

  • Before
    DateTime.Now.Is().Before(DateTime.Now.AddDays(1))
  • After
    DateTime.Now.Is().After(DateTime.Now.AddDays(-1))
  • CloseTo
    DateTime.Now.Is().CloseTo(DateTime.Now.AddDays(1), TimeSpan.FromDays(2))
    TimeSpan.FromDays(4).Is().CloseTo(TimeSpan.FromDays(3), TimeSpan.FromDays(2))
  • Positive
    TimeSpan.FromDays(1).Is().Positive()
  • Negative
    TimeSpan.FromDays(1).Is().Negative()

Collections

Is
  • EqualTo
    all elements are equal and in the same order
    list.Is().EqualTo(otherList)
  • Like
    all elements are equal but order may differ
    list.Is().Like(otherList)
  • SameAs
    reference equal
    list.Is().SameAs(otherList)
  • EquivalentTo
    all elements are equal but order may differ
    list.Is().EquivalentTo(otherList)
  • Empty
    list.Is().Empty()
  • Distinct
    list.Is().Distinct() // all elements in the collection are different
Does
  • Contain
    list.Does().Contain(3)
Has
  • Count
    list.Has().Count(3)
    list.Has().Count(it => it > 3).At(2) // with condition
  • Count at least
    list.Has().Count().AtLeast(2)
    list.Has().Count(it => it > 3).AtLeast(2) // with condition
  • Count at most
    list.Has().Count().AtMost(2)
    list.Has().Count(it => it > 3).AtMost(2) // with condition
  • Count in range
    list.Has().Count().InRange(2, 4)
    list.Has().Count(it => it > 3).InRange(2, 4) // with condition
  • Order ascending
    list.Has().Order().Ascending()
    list.Has().Count(it => it.Age).Ascending() // with condition
  • Order descending
    list.Has().Order().Descending()
    list.Has().Count(it => it.Age).Descending() // with condition
  • [One/Two/Three/Four/Five]Items
    verify that the collection has the given number of items and return them as a n-tuple
    list.Has().OneItem().That.Age.Is(3)
    list.Has().OneItem(it => it.Age == 3).That.Gender.Is('F') - with filter
  • All
    list.Has().All(it => it.Age > 3) // all items in the collection match the criteria
    list.Has().All((it, i) => it.Age > i) // with index of item
    list.Has().All(it => it.Age.Is().GreaterThan(3)) // apply assertion to all items
  • Some
    list.Has().Some(it => it.Age > 3) - at least one item in the collection matches the criteria
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.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
19.0.1 28 1/2/2026
19.0.0 36 1/2/2026
18.12.0 35 1/1/2026
18.11.0 270 12/17/2025
18.10.1 206 12/6/2025
18.10.0 176 12/5/2025
18.9.4 211 10/15/2025
18.9.3 197 10/14/2025
18.9.2 148 10/4/2025
18.9.1 215 9/20/2025
18.8.3 171 9/11/2025
18.8.2 566 8/9/2025
18.8.1 306 8/6/2025
18.8.0 651 7/23/2025
18.7.2 118 7/19/2025
18.7.1 111 7/19/2025
18.7.0 112 7/19/2025
18.6.5 149 7/13/2025
18.6.4 141 7/12/2025
18.6.3 194 7/9/2025
18.6.2 135 7/5/2025
18.6.1 197 7/1/2025
18.6.0 192 6/30/2025
18.5.0 233 5/25/2025
18.4.0 694 5/4/2025
18.3.0 670 3/18/2025
18.3.0-preview 186 3/17/2025
18.2.1 194 3/14/2025
18.2.0 266 3/11/2025
18.1.1 380 3/8/2025
18.1.0 364 3/4/2025
18.0.0 261 2/25/2025
17.2.1 369 1/21/2025
17.2.0 180 1/18/2025
17.1.5 318 11/10/2024
17.1.4 198 11/10/2024
17.1.3 171 11/3/2024
17.1.2 200 11/2/2024
17.1.1 338 10/29/2024
17.1.0 178 10/28/2024
17.0.3 229 10/14/2024
17.0.2 184 10/14/2024
17.0.1 175 10/14/2024
17.0.0 206 10/12/2024
17.0.0-pre.3 121 10/8/2024
17.0.0-pre.2 109 10/6/2024
17.0.0-pre.1 121 10/6/2024
16.4.1 205 10/5/2024
16.4.0 233 10/4/2024
16.3.1 191 9/22/2024
16.3.0 200 9/22/2024
16.2.1 381 9/14/2024
16.2.0 202 9/14/2024
16.1.5 179 9/7/2024
16.1.4 188 9/7/2024
16.1.3 459 9/7/2024
16.1.2 187 9/6/2024
16.1.1 191 9/3/2024
16.1.0 194 9/2/2024
16.0.4 510 8/18/2024
16.0.4-preview 180 8/18/2024
16.0.3-preview 167 8/18/2024
16.0.2-preview 186 8/17/2024
16.0.1-preview 195 8/17/2024
16.0.0-preview 188 8/16/2024
15.7.0 326 8/7/2024
15.6.2 177 7/29/2024
15.6.1 325 7/14/2024
15.6.0 179 7/13/2024
15.5.4 264 7/7/2024
15.5.3 215 7/7/2024
15.5.2 204 7/7/2024
15.5.1 208 7/2/2024
15.5.0 201 6/30/2024
15.4.1 205 6/29/2024
15.4.0 227 6/24/2024
15.3.2 216 6/24/2024
15.3.1 220 6/23/2024
15.3.0 223 6/23/2024
15.2.1 243 6/20/2024
15.2.0 206 6/19/2024
15.1.3-preview 183 6/19/2024
15.1.2 222 6/18/2024
15.1.1 211 6/17/2024
15.1.0 230 6/16/2024
15.0.1 209 6/15/2024
15.0.0 195 6/9/2024
14.2.1 209 6/6/2024
14.2.0 206 6/6/2024
14.1.0 205 5/13/2024
14.0.0 203 5/9/2024
13.3.2 310 4/7/2024
13.3.1 222 1/31/2024
13.3.0 447 1/20/2024
13.2.3 230 1/15/2024
13.2.2 212 1/13/2024
13.2.1 241 1/2/2024
13.2.0 287 1/2/2024
13.1.2 235 12/19/2023
13.1.1 263 12/19/2023
13.1.0 221 12/18/2023
13.0.1 185 12/17/2023
13.0.0 220 12/17/2023
12.2.2 202 12/16/2023
12.2.1 205 12/16/2023
12.2.0 194 12/16/2023
12.1.1 223 12/16/2023
12.1.0 220 12/3/2023
12.0.0 190 12/2/2023
11.0.4 216 11/28/2023
11.0.3 284 11/19/2023
11.0.2 206 11/19/2023
11.0.1 203 11/18/2023
11.0.0 209 11/18/2023
10.0.2 219 11/18/2023
10.0.1 210 11/15/2023
10.0.0 204 11/12/2023
9.3.2 194 11/12/2023
9.3.1 178 11/12/2023
9.3.0 220 11/11/2023
9.2.1 187 11/11/2023
9.2.0 200 11/5/2023
9.1.1 217 10/29/2023
9.1.0 233 10/28/2023
9.0.0 236 10/28/2023
8.5.1 214 10/27/2023
8.5.0 219 10/26/2023
8.4.0 204 10/22/2023
8.3.1 243 10/22/2023
8.3.0 201 10/22/2023
8.2.1 227 10/22/2023
8.2.0 245 10/21/2023
8.1.2 230 10/21/2023
8.1.1 221 10/20/2023
8.1.0 206 10/20/2023
8.0.1 234 10/18/2023
8.0.0 212 10/16/2023
7.2.0 231 10/16/2023
7.1.1 222 10/12/2023
7.1.0 249 10/8/2023
7.0.1 202 10/1/2023
7.0.0 218 10/1/2023
6.4.0 245 9/30/2023
6.3.2 194 9/30/2023
6.3.1 213 9/30/2023
6.3.0 190 9/25/2023
6.2.4 204 9/15/2023
6.2.3 242 9/15/2023
6.2.2 201 9/15/2023
6.2.1 219 9/15/2023
6.2.0 209 9/14/2023
6.1.3 222 9/13/2023
6.1.2 259 9/12/2023
6.1.1 226 9/12/2023
6.1.0 271 9/10/2023
6.0.0 245 9/9/2023
5.5.0 231 9/8/2023
5.4.3 234 9/7/2023
5.4.2 251 9/5/2023
5.4.1 231 9/3/2023
5.4.0 310 8/28/2023
5.3.1 270 8/28/2023
5.3.0 232 8/27/2023
5.2.0 241 8/27/2023
5.1.1 263 8/26/2023
5.1.0 273 8/26/2023
5.0.0 248 8/26/2023
4.5.2 245 8/26/2023
4.5.1 234 8/26/2023
4.5.0 244 8/26/2023
4.4.7 229 8/22/2023
4.4.6 218 8/22/2023
4.4.5 242 8/21/2023
4.4.4 249 8/20/2023
4.4.3 263 8/16/2023
4.4.2 282 8/15/2023
4.4.1 268 8/15/2023
4.4.0 261 8/15/2023
4.3.1 276 8/14/2023
4.3.0 288 8/14/2023
4.2.0 279 8/14/2023
4.1.1 272 8/11/2023
4.1.0 277 8/9/2023
4.0.0 273 8/8/2023
3.3.2 248 8/7/2023
3.3.1 281 8/6/2023
3.3.0 267 8/6/2023
3.2.1 277 8/6/2023
3.2.0 287 8/6/2023
3.1.0 300 8/5/2023
3.0.0 293 8/2/2023
2.4.1 262 8/1/2023
2.4.0 258 8/1/2023
2.3.1 268 7/30/2023
2.3.0 261 7/30/2023
2.2.3 245 7/29/2023
2.2.2 279 7/28/2023
2.2.1 255 7/24/2023
2.2.0 296 7/24/2023
2.1.1 288 7/23/2023
2.1.0 299 7/23/2023
2.0.1 292 7/21/2023
2.0.0 299 7/21/2023
1.1.0 306 7/20/2023
1.0.0 277 7/20/2023

Added details on the assertion framework to README