I am delighted to announce that MSpec (Machine.Specifications) is now available for .NET Standard 1.3+ together with a new .NET CLI (dotnet test
) test runner for .NET Core and .NET 4.5+.
If you are not sure what MSpec is - scroll down for a quick intro. In short it’s a “context/specification” test framework.
This is backwards compatible version, so there shouldn’t be any issues using this version with ReSharper, CodeRush or other test runners when targeting the .NET Framework.
Package | Version | NuGet |
---|---|---|
dotnet-test-mspec | 0.2.0 | NuGet |
Machine.Specifications | 0.11.0 | NuGet |
Machine.Specifications.Should | 0.11.0 | NuGet |
Machine.Fakes | 2.8.0-beta3 | NuGet |
Machine.Fakes.Moq | 2.8.0-beta3 | NuGet |
Machine.Fakes.Moq is in beta due to the underlying Moq dependency not having yet a stable release for .NET Standard.
Useful Links
Getting started on .Net Core and .NET CLI
Here is an example project.json
of a tests project which targets both .NET Core and .NET 4.5. As you can see it includes:
- Dependency on
Machine.Specifications
anddotnet-test-mspec
"testRunner": "mspec"
project.json
{
"testRunner": "mspec",
"frameworks": {
"net45": { },
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.*",
"type": "platform"
}
}
}
},
"dependencies": {
"FluentAssertions": "4.12.0",
"Machine.Specifications": "0.11.0",
"dotnet-test-mspec": "*"
}
}
We can now use dotnet test
to run the tests:
> dotnet test
Project Example (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
dotnet-test-mspec: 0.2.0
Machine.Specifications: 0.11.0.0
Specs in Example:
...***
Contexts: 3, Specifications: 6, Time: 0.13 seconds
3 passed, 0 failed, 3 not implemented
Project Example (.NETFramework,Version=v4.5) was previously compiled. Skipping compilation.
dotnet-test-mspec: 0.2.0
Machine.Specifications: 1.0.*
Specs in Example:
...***
Contexts: 3, Specifications: 6, Time: 0.09 seconds
3 passed, 0 failed, 3 not implemented
SUMMARY: Total: 2 targets, Passed: 2, Failed: 0.
Known Issues with the dotnet cli (dotnet test) runner
- IDE integration (aka Design-Time) is not yet implemented, so you won’t be able to see or run test in Visual Studio IDE: #303
- The console output format doesn’t have colours and enough new line padding, etc. This is an upstream issue with
dotnet test
and hopefully will be fixed in the next preview. #311. This is potentially blocking the TeamCity and AppVeyor auto-detect outputs.
Hopefully they should land in v0.3 of the test runner.
What is MSpec?
MSpec is called a “context/specification” test framework because of the “grammar” that is used in describing and coding the tests or “specs”. That grammar reads roughly like this
When the system is in such a state, and a certain action occurs, it should do such-and-such or be in some end state.
You should be able to see the components of the traditional Arrange-Act-Assert model in there. To support readability and remove as much “noise” as possible, MSpec eschews the traditional attribute-on-method model of test construction. It instead uses custom .NET delegates that you assign anonymous methods and asks you to name them following a certain convention.
You can read more about MSpec in the docs here.
[Subject("Authentication")]
public class When_authenticating_an_admin_user
{
Establish context = () => {
Subject = new SecurityService();
};
Because of = () => {
Token = Subject.Authenticate("username", "password");
};
It should_indicate_the_users_role = () => {
Token.Role.ShouldEqual(Roles.Admin);
};
It should_have_a_unique_session_id = () => {
Token.SessionId.ShouldNotBeNull();
};
static SecurityService Subject;
static UserToken Token;
}
and an example of Machine.Fakes usage in MSpec:
[Subject("Authentication")]
public class When_authenticating_an_admin_user : WithFakes
{
Establish context = () => {
Subject = new SecurityService(The<SecurityClient>());
The<SecurityClient>()
.WhenToldTo(client => client.SendRequest(Param<string>.IsAny))
.Returns(true);
};
Because of = () => {
Subject.Authenticate("username", "password");
};
It should_request_a_token = () => {
The<SecurityClient>()
.WasTaldTo(client => client.RequestToken());
};
static SecurityService Subject;
}