XspecT 16.3.0
See the version list below for details.
dotnet add package XspecT --version 16.3.0
NuGet\Install-Package XspecT -Version 16.3.0
<PackageReference Include="XspecT" Version="16.3.0" />
paket add XspecT --version 16.3.0
#r "nuget: XspecT, 16.3.0"
// Install XspecT as a Cake Addin #addin nuget:?package=XspecT&version=16.3.0 // Install XspecT as a Cake Tool #tool nuget:?package=XspecT&version=16.3.0
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);
}
Recommended conventions
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 | Versions 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. |
-
net8.0
- AutoFixture (>= 4.18.1)
- FluentAssertions (>= 6.12.1)
- Moq (>= 4.20.72)
- Moq.AutoMock (>= 3.5.0)
- xunit (>= 2.9.0)
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 | 96 | 11/10/2024 |
17.1.4 | 63 | 11/10/2024 |
17.1.3 | 66 | 11/3/2024 |
17.1.2 | 65 | 11/2/2024 |
17.1.1 | 128 | 10/29/2024 |
17.1.0 | 77 | 10/28/2024 |
17.0.3 | 112 | 10/14/2024 |
17.0.2 | 73 | 10/14/2024 |
17.0.1 | 73 | 10/14/2024 |
17.0.0 | 82 | 10/12/2024 |
17.0.0-pre.3 | 42 | 10/8/2024 |
17.0.0-pre.2 | 38 | 10/6/2024 |
17.0.0-pre.1 | 40 | 10/6/2024 |
16.4.1 | 88 | 10/5/2024 |
16.4.0 | 108 | 10/4/2024 |
16.3.1 | 81 | 9/22/2024 |
16.3.0 | 86 | 9/22/2024 |
16.2.1 | 212 | 9/14/2024 |
16.2.0 | 91 | 9/14/2024 |
16.1.5 | 86 | 9/7/2024 |
16.1.4 | 93 | 9/7/2024 |
16.1.3 | 156 | 9/7/2024 |
16.1.2 | 84 | 9/6/2024 |
16.1.1 | 97 | 9/3/2024 |
16.1.0 | 94 | 9/2/2024 |
16.0.4 | 253 | 8/18/2024 |
16.0.4-preview | 107 | 8/18/2024 |
16.0.3-preview | 102 | 8/18/2024 |
16.0.2-preview | 106 | 8/17/2024 |
16.0.1-preview | 107 | 8/17/2024 |
16.0.0-preview | 102 | 8/16/2024 |
15.7.0 | 173 | 8/7/2024 |
15.6.2 | 75 | 7/29/2024 |
15.6.1 | 174 | 7/14/2024 |
15.6.0 | 86 | 7/13/2024 |
15.5.4 | 146 | 7/7/2024 |
15.5.3 | 100 | 7/7/2024 |
15.5.2 | 99 | 7/7/2024 |
15.5.1 | 97 | 7/2/2024 |
15.5.0 | 102 | 6/30/2024 |
15.4.1 | 98 | 6/29/2024 |
15.4.0 | 116 | 6/24/2024 |
15.3.2 | 92 | 6/24/2024 |
15.3.1 | 110 | 6/23/2024 |
15.3.0 | 111 | 6/23/2024 |
15.2.1 | 111 | 6/20/2024 |
15.2.0 | 114 | 6/19/2024 |
15.1.3-preview | 94 | 6/19/2024 |
15.1.2 | 107 | 6/18/2024 |
15.1.1 | 123 | 6/17/2024 |
15.1.0 | 122 | 6/16/2024 |
15.0.1 | 105 | 6/15/2024 |
15.0.0 | 104 | 6/9/2024 |
14.2.1 | 99 | 6/6/2024 |
14.2.0 | 96 | 6/6/2024 |
14.1.0 | 107 | 5/13/2024 |
14.0.0 | 98 | 5/9/2024 |
13.3.2 | 223 | 4/7/2024 |
13.3.1 | 115 | 1/31/2024 |
13.3.0 | 330 | 1/20/2024 |
13.2.3 | 120 | 1/15/2024 |
13.2.2 | 115 | 1/13/2024 |
13.2.1 | 122 | 1/2/2024 |
13.2.0 | 168 | 1/2/2024 |
13.1.2 | 128 | 12/19/2023 |
13.1.1 | 162 | 12/19/2023 |
13.1.0 | 133 | 12/18/2023 |
13.0.1 | 106 | 12/17/2023 |
13.0.0 | 117 | 12/17/2023 |
12.2.2 | 118 | 12/16/2023 |
12.2.1 | 110 | 12/16/2023 |
12.2.0 | 116 | 12/16/2023 |
12.1.1 | 122 | 12/16/2023 |
12.1.0 | 144 | 12/3/2023 |
12.0.0 | 134 | 12/2/2023 |
11.0.4 | 136 | 11/28/2023 |
11.0.3 | 234 | 11/19/2023 |
11.0.2 | 129 | 11/19/2023 |
11.0.1 | 134 | 11/18/2023 |
11.0.0 | 131 | 11/18/2023 |
10.0.2 | 141 | 11/18/2023 |
10.0.1 | 127 | 11/15/2023 |
10.0.0 | 135 | 11/12/2023 |
9.3.2 | 121 | 11/12/2023 |
9.3.1 | 121 | 11/12/2023 |
9.3.0 | 130 | 11/11/2023 |
9.2.1 | 133 | 11/11/2023 |
9.2.0 | 134 | 11/5/2023 |
9.1.1 | 140 | 10/29/2023 |
9.1.0 | 143 | 10/28/2023 |
9.0.0 | 152 | 10/28/2023 |
8.5.1 | 146 | 10/27/2023 |
8.5.0 | 146 | 10/26/2023 |
8.4.0 | 160 | 10/22/2023 |
8.3.1 | 157 | 10/22/2023 |
8.3.0 | 150 | 10/22/2023 |
8.2.1 | 146 | 10/22/2023 |
8.2.0 | 136 | 10/21/2023 |
8.1.2 | 141 | 10/21/2023 |
8.1.1 | 139 | 10/20/2023 |
8.1.0 | 128 | 10/20/2023 |
8.0.1 | 151 | 10/18/2023 |
8.0.0 | 133 | 10/16/2023 |
7.2.0 | 141 | 10/16/2023 |
7.1.1 | 136 | 10/12/2023 |
7.1.0 | 161 | 10/8/2023 |
7.0.1 | 133 | 10/1/2023 |
7.0.0 | 130 | 10/1/2023 |
6.4.0 | 151 | 9/30/2023 |
6.3.2 | 120 | 9/30/2023 |
6.3.1 | 144 | 9/30/2023 |
6.3.0 | 121 | 9/25/2023 |
6.2.4 | 143 | 9/15/2023 |
6.2.3 | 135 | 9/15/2023 |
6.2.2 | 125 | 9/15/2023 |
6.2.1 | 153 | 9/15/2023 |
6.2.0 | 140 | 9/14/2023 |
6.1.3 | 151 | 9/13/2023 |
6.1.2 | 164 | 9/12/2023 |
6.1.1 | 145 | 9/12/2023 |
6.1.0 | 172 | 9/10/2023 |
6.0.0 | 145 | 9/9/2023 |
5.5.0 | 156 | 9/8/2023 |
5.4.3 | 141 | 9/7/2023 |
5.4.2 | 164 | 9/5/2023 |
5.4.1 | 134 | 9/3/2023 |
5.4.0 | 233 | 8/28/2023 |
5.3.1 | 168 | 8/28/2023 |
5.3.0 | 146 | 8/27/2023 |
5.2.0 | 164 | 8/27/2023 |
5.1.1 | 162 | 8/26/2023 |
5.1.0 | 158 | 8/26/2023 |
5.0.0 | 167 | 8/26/2023 |
4.5.2 | 145 | 8/26/2023 |
4.5.1 | 150 | 8/26/2023 |
4.5.0 | 142 | 8/26/2023 |
4.4.7 | 154 | 8/22/2023 |
4.4.6 | 138 | 8/22/2023 |
4.4.5 | 134 | 8/21/2023 |
4.4.4 | 166 | 8/20/2023 |
4.4.3 | 159 | 8/16/2023 |
4.4.2 | 168 | 8/15/2023 |
4.4.1 | 165 | 8/15/2023 |
4.4.0 | 185 | 8/15/2023 |
4.3.1 | 165 | 8/14/2023 |
4.3.0 | 185 | 8/14/2023 |
4.2.0 | 175 | 8/14/2023 |
4.1.1 | 166 | 8/11/2023 |
4.1.0 | 163 | 8/9/2023 |
4.0.0 | 179 | 8/8/2023 |
3.3.2 | 162 | 8/7/2023 |
3.3.1 | 159 | 8/6/2023 |
3.3.0 | 165 | 8/6/2023 |
3.2.1 | 160 | 8/6/2023 |
3.2.0 | 198 | 8/6/2023 |
3.1.0 | 192 | 8/5/2023 |
3.0.0 | 181 | 8/2/2023 |
2.4.1 | 177 | 8/1/2023 |
2.4.0 | 165 | 8/1/2023 |
2.3.1 | 172 | 7/30/2023 |
2.3.0 | 163 | 7/30/2023 |
2.2.3 | 164 | 7/29/2023 |
2.2.2 | 174 | 7/28/2023 |
2.2.1 | 170 | 7/24/2023 |
2.2.0 | 179 | 7/24/2023 |
2.1.1 | 177 | 7/23/2023 |
2.1.0 | 176 | 7/23/2023 |
2.0.1 | 178 | 7/21/2023 |
2.0.0 | 186 | 7/21/2023 |
1.1.0 | 194 | 7/20/2023 |
1.0.0 | 160 | 7/20/2023 |
Removed obsolete methods + improved documentation