Woksin.Extensions.Specifications.MSTest
2.0.0
See the version list below for details.
dotnet add package Woksin.Extensions.Specifications.MSTest --version 2.0.0
NuGet\Install-Package Woksin.Extensions.Specifications.MSTest -Version 2.0.0
<PackageReference Include="Woksin.Extensions.Specifications.MSTest" Version="2.0.0" />
paket add Woksin.Extensions.Specifications.MSTest --version 2.0.0
#r "nuget: Woksin.Extensions.Specifications.MSTest, 2.0.0"
// Install Woksin.Extensions.Specifications.MSTest as a Cake Addin #addin nuget:?package=Woksin.Extensions.Specifications.MSTest&version=2.0.0 // Install Woksin.Extensions.Specifications.MSTest as a Cake Tool #tool nuget:?package=Woksin.Extensions.Specifications.MSTest&version=2.0.0
Specifications framework
This project represents a way to do Specification by Example - BDD style inspired by the conciseness of Machine.Specifications.
What does it do?
In BDD one talks about the given, when, then. Much like arrange, act and assert in a way that
is more common in TDD. The biggest difference is on a mindset level of thinking in specifications of behaviors in your
system. What this particular library delivers is a way to do these and also keep in line with what is common in the BDD
world of having isolated specifications and not have typically a FooTests and dump all your tests for the unit Foo
in
it.
The library supports convention lifecycle methods Establish()
, Because()
and Destroy()
. There is no virtual method
to override, just match the expected signatures:
Signature | Purpose |
---|---|
void Establish() | Establishes the current context - given / arrange |
void Because() | Triggers the behavior being specified - when / act |
void Destroy() | Tears down the context |
If your specification requires to run in an async context, it also supports the following:
Signature | Purpose |
---|---|
Task Establish() | Establishes the current context - given / arrange |
Task Because() | Triggers the behavior being specified - when / act |
Task Destroy() | Tears down the context |
All lifecycle methods are optional and will be ignored if not there. Multiple levels of inheritance recursively is supported, meaning that specifications will run all the lifecycle methods from the proper level in the hierarchy chain and up or down the hierarchy depending on whether it is arrange/act or teardown.
To get all this to work, all you need to do is inherit from the Specification
type found in Woksin.Extensions.Specifications.<Testframework>
.
Structure and naming
The general purpose of BDD and specification by example is to make it all very human readable and possible to navigate quite
easily. New developers can come into the solution and pretty much read up on the specifications and get a glimpse of how the
system works. So rather than having a FooTests class with all the tests, it is recommended to have folders describing the scenario being
specified. For a unit this could be named for_<name of unit>
e.g. : for_SecurityService
. If you're testing a more domain
centric scenario in your system that involves multiple units, the folder name would reflect the name of the scenario e.g.:
for_logging_in_users
.
Within these folders you'd keep your when statements. E.g. When_authenticating_an_admin_user. If you want to group things, for instance lets say you have multiple behaviors within the concept of authenticating, you could then have a folder grouping these called When_authenticating and then drop in the behavior specifications within this folder an_admin_user and a_null_user.
In addition to this you might want to reuse a context. This can quite easily be achieved through inheritance. Structure-wise you'd then have a given folder and namespace where you'd put the common reusable context - again reflecting what it represents, for instance for our authentication scenario: no_user_authenticated.
For a sample of how this looks like, look within the test folders here folder.
Compiler Warnings
Since the naming of classes, methods and structure deviates from what is expected by default from the C# compiler, you typically
end up getting a lot of warnings. These can be turned off by adding a NoWarn element within a PropertyGroup to your .csproj
file:
<PropertyGroup>
<NoWarn>CA1707;CS1591;RCS1213;IDE0051;IDE1006;CA1051</NoWarn>
</PropertyGroup>
Warning | Description |
---|---|
CA1707 | Identifiers should not contain underscores |
CA1051 | Do not declare visible instance fields |
CS1591 | Missing XML comment for publicly visible type or member 'Type_or_Member' |
IDE0051 | Remove unused private member |
IDE1006 | Naming rule violation |
RCS1213 | Remove unused member declaration |
If you're using static code analysis and stylecop and have turned on all rules by default, you might also encounter the following that you want to turn off:
Warning | Description |
---|---|
SA1633 | File header copyright text must match |
SA1649 | File name must match type name |
SA1600 | Elements must be documented |
SA1310 | Field names must not contain underscore |
SA1502 | Element must not be on a single line |
SA1134 |
Depending on your solution, you might want to consider suppressnig the following.
Warning | Description |
---|---|
RCS1090 | Add call to 'ConfigureAwait'. |
Example
class When_authenticating_an_admin_user : Specification
{
SecurityService subject;
UserToken user_token;
void Establish() =>
subject = new SecurityService();
void Because() =>
user_token = subject.Authenticate("username", "password");
[Fact] void should_indicate_the_users_role() =>
user_token.Role.ShouldEqual(Roles.Admin);
[Fact] void should_have_a_unique_session_id() =>
user_token.SessionId.ShouldNotBeNull();
}
Catching an exception and testing for the correct exception:
class When_authenticating_a_null_user : Specification
{
SecurityService subject;
Exception result;
void Establish() =>
subject = new SecurityService();
void Because() =>
result = Catch.Exception(() => subject.Authenticate(null, null));
[Fact] void should_throw_user_must_be_specified_exception() =>
result.ShouldBeOfExactType<UserMustBeSpecified>();
}
Building reusable contexts (in a sub-namespace with given):
class no_user_authenticated
{
protected SecurityService subject;
void Establish() =>
subject = new SecurityService();
}
Refactor one of the specifications:
class When_authenticating_a_null_user : given.no_user_authenticated
{
Exception result;
void Because() =>
result = Catch.Exception(() => subject.Authenticate(null, null));
[Fact] void should_throw_user_must_be_specified_exception() =>
result.ShouldBeOfExactType<UserMustBeSpecified>();
}
Supports teardown through Destroy
:
class no_user_authenticated
{
protected SecurityService subject;
void Establish() =>
subject = new SecurityService();
void Destroy() => subject.Dispose();
}
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 is compatible. 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 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. |
-
net6.0
- MSTest.TestFramework (>= 3.1.1)
- Woksin.Extensions.Specifications (>= 2.0.0)
-
net7.0
- MSTest.TestFramework (>= 3.1.1)
- Woksin.Extensions.Specifications (>= 2.0.0)
-
net8.0
- MSTest.TestFramework (>= 3.1.1)
- Woksin.Extensions.Specifications (>= 2.0.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 2.0.0:
Adds mulit-tenancy support and build for .NET 8!
Added:
- .NET 8 support
- Multi-tenancy support - Adds multi-tenancy support to IoC and Configuration.Tenancy packages- TenantId type which is simply represented by a string. Meant to uniquely identify a tenant
- PerTenant attribute used in both the IoC and Configuration system to signify a tenant-scoped dependency
- Tenant-scoped service providers - Each tenant owns its own scoped dependencies with their own lifetimes. Think of it as a completely isolated IoC containers per tenant
- Tenant-scoped configuration - Allows for resolving configurations for each tenant
- Automatic JSON serialization for TenantId type for both Microsoft MVC and WebAPI resolving the tenant id from a string
- Automatic serialization of TenantId type from IConfiguration (meaning that a configuration class can have TenantId as property resolved from a string)
- Adds a Middleware that resolved the TenantId for each request based on a strategies- Default strategy if no custom strategy is configured is to find the TenantId in the Tenant-Id header in the HttpRequest
- Can provide custom strategies that will be used for resolving TenantId from HttpContext
- TenantId filters can also be added to filter out tenant ids resolved from the strategies
- Woksin.Extensions.Configuration.Tenancy nuget package
Changed:
- Woksin.Extensions.Configuration project structure to support the multi-tenancy model from the IoC package. Split into multiple projects:- Woksin.Extensions.Configuration.Core - Provides the common functionality used by the configuration extension packages
- Woksin.Extensions.Configuration.Base - Is the old configuration system without multi-tenancy support
- Woksin.Extensions.Configuration.Tenancy - Adds multi-tenancy support by depending on the IoC system
- Various breaking changes in classes, class names, etc. That should not impact existing applications using these packages
Changelog:
For all release notes, see changelog (https://github.com/woksin-org/Woksin.Extensions/blob/main/Packages/DotNET/Specifications/CHANGELOG.md)