XspecT 1.1.0
See the version list below for details.
dotnet add package XspecT --version 1.1.0
NuGet\Install-Package XspecT -Version 1.1.0
<PackageReference Include="XspecT" Version="1.1.0" />
paket add XspecT --version 1.1.0
#r "nuget: XspecT, 1.1.0"
// Install XspecT as a Cake Addin #addin nuget:?package=XspecT&version=1.1.0 // Install XspecT as a Cake Tool #tool nuget:?package=XspecT&version=1.1.0
XspecT: A fluent unit testing framework
Framework for writing and running automated tests in .Net in a flexible and fluent style, based on the popular "Given-When-Then" pattern, built upon XUnit, Moq, AutoMock and FluentAssertions.
Whether you are beginner or expert in unit-testing, this framework will help you to write more descriptive tests with less code.
Usage
It is assumed that you are already familiar with Xunit and Moq, or similar test and mocking frameworks.
This package includes FluentAssertions, but also comes with its own, more limited but less wordy assertion methods, based on the verb Is
instead of Should
.
Is-assertions have the same return-types as Should-assertions, so they can be combined in the same sentence.
Is-assertions are recommended to make the tests read more like specifications, stating facts rather than expectations.
For instance When(SUT.AddNumbers(X, Y)).Given((X, Y) = (1, 2).Then.Result.Is(3)
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 to write minimalistic tests with a minimum of clutter.
First you create your test-class, or Specification, overriding StaticSpec<[TReturn]>
with the expected return type as generic argument.
Then create a test pipeline by calling When
with a lambda expression to test.
Finally you can verify the result by calling Then
on the returned pipeline and check the result with Is
Example:
public class CalculatorSpec : StaticSpec<int>
{
[Theory]
[InlineData(1, 1, 2)]
[InlineData(3, 4, 7)]
public void WhenAddThenReturnSum(int x, int y, int sum)
=> When(() => Calculator.Add(x, y)).Result.Is(sum);
[Theory]
[InlineData(1, 1, 1)]
[InlineData(3, 4, 12)]
public void WhenMultiplyThenReturnProduct(int x, int y, int product)
=> When(() => Calculator.Multiply(x, y)).Result.Is(product);
}
Recommended conventions
For more complex and realistic scenarious, it is recommended to create tests in a separate project from the production code, named [MyProject].Test
.
The test-project should mimmic 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.
Test a static method with [Fact]
To test a static method
[MyClass].[MyMethod]
returning[ReturnType]
, Create a new abstract class namedWhen[MyMethod]
inheritingStaticSpec<[ReturnType]>
.Create a parameterless constructor that calls
When
with a lambda expression calling the method to test:When(() => [MyClass].[MyMethod]([Args...]))
.Each argument should be defined as a protected field at the top of the class.
For each set of preconditions you want to test, Create a nested class within the When-class with the name
Given[SomePrecondition]
inheriting the outer class.Override
Set
or callGiven
to assign values to the arguments of the method-to-test.Finally, create a test method, attributed with [Fact], for each logical assertion you want to make.
The assertion should first call Then, which executes the test and returns the result.
If there is only one test method in the Given-class, the call to
Given
can be placed in the chain beforeThen
You can for instance test if a certain value was returned by writing
Result.Is([SomeValue])
.
Example:
namespace MyProject.Test.Calculator;
public abstract class WhenAddTwoNumbers : StaticSpec<int>
{
protected int X, Y;
public WhenAddTwoNumbers() => When(() => MyProject.Calculator.Add(X, Y));
public class Given_1_And_1 : WhenAdd // Using Set to arrange
{
protected override void Set() => (X, Y) = (1, 1);
[Fact] public void ThenReturn_3() => Result.Is(3);
}
public class Given_3_And_4 : WhenAdd // Using Given to arrange
{
[Fact] public void ThenReturn_7() => Given(() => (X, Y) = (3, 4)).Result.Is(7);
}
}
Test a static void method
- When testing a static void method, there is no return value to verify in result and by convention the generic TResult parameter is set to object.
- However you can us
Throws
orNotThrows
to verify exceptions thrown.
Example:
namespace MyProject.Test.Validator;
public abstract class WhenVerifyAreEqual : StaticSpec<object>
{
protected int X, Y;
protected WhenVerifyAreEqual() => When(() => MyProject.Validator.VerifyAreEqual(X, Y));
public class Given_1_And_2 : WhenVerifyAreEqual
{
[Fact] public void ThenThrows_NotEqual() => Given(() => (X, Y) = (1, 2)).Then.Throws<NotEqual>();
}
public class Given_2_And_2 : WhenVerifyAreEqual
{
[Fact] public void ThenDoNotThrow() => Given(() => (X, Y) = (2, 2)).Then.NotThrows();
}
}
Test a class with dependencies
To test an instance method
[MyClass].[MyMethod]
, inheritXspecT.SubjectSpec<[MyClass], TResult>
.It is recommended practice to create a common baseclass for all tests of
[MyClass]
, named[MyClass]Spec
.The subject under test (sut) will be created automatically with mocks and default values by AutoMock. You can supply you own constructor arguments by calling
Using
(which will be applied in the same order when test pipeline is executed).For each method to test, create an abstract class named
When[MyMethod]
inheriting[MyClass]Spec
in the same way as for static methods.To mock behaviour of any dependency, either override
Setup
or provide the mocking by callingGiven
. Each call toGiven
will provide additional arrangement that will be applied on test execution on the inversed order.The framework gives you direct access to one (lazily generated) mock each of any class type type. You can access a mock by
The<MyMockedInterface>()
.To verify a call to a dependency, write
Then.Does<MyMockedInterface>([SomeLambdaExpression])
.Moq framework is used to express both mocking and verification of behaviour.
Example:
namespace MyProject.Test.ShoppingService;
public abstract class ShoppingServiceSpec<TResult> : SubjectSpec<MyProject.ShoppingService, TResult>
{
protected ShoppingServiceSpec() => Using(new MyTestLogger());
}
public abstract class WhenPlaceOrder : ShoppingServiceSpec<object>
{
protected ShoppingCart Cart;
protected WhenPlaceOrder()
=> When(() => SUT.PlaceOrder(Cart)).Given(() => The<ICartRepository>().ReturnsDefault(Cart));
public class GivenCart : WhenPlaceOrder
{
[Fact] public void ThenOrderIsCreated()
=> Given(() => Cart = new()).Then.Does<IOrderService>(_ => _.CreateOrder(Cart));
}
}
Test async methods
All the examples above also works for async methods, with small modifications.
More examples can be found as Unit tests in the source code.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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. |
-
net6.0
- FluentAssertions (>= 6.11.0)
- Moq (>= 4.18.4)
- Moq.AutoMock (>= 3.5.0)
- xunit (>= 2.5.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 | 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 |
Use default values when method arguments were not specified before executing test pipeline