# Machine.Specifications replaces attributes with named delegates in .NET tests

> MSpec is a .NET test framework that describes tests with a context/specification grammar, using named delegates instead of attributes on methods. It suits teams that want readable test names and less ceremony, and it asks you to accept a non-standard vocabulary.

**machine/machine.specifications** — Machine.Specifications is a Context/Specification framework for .NET that removes language noise and simplifies tests.

- Repository: https://github.com/machine/machine.specifications
- Stars: 897 · Forks: 176
- Language: C#
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/machine-machine-specifications

## The grammar is the point

MSpec is called a context/specification test framework because of the grammar used in describing and coding the tests, and the grammar is stated in the README as a single sentence: 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.

The components of the traditional Arrange-Act-Assert model are visible in that sentence, and the framework's aim is to keep them while removing as much noise as possible. The stated method for doing that is to eschew the traditional attribute-on-method model of test construction. Instead it uses custom delegates that you assign anonymous methods, and asks you to name them following a certain convention.

So a test is a class, the class is named after the situation, and the members are named after what they establish, what causes, and what should follow. The consequence is that a failing test prints a sentence you can read without opening the file. A test named It_should_indicate_the_users_role failed, and you know what the system was doing and what it did instead.

That is a real benefit in a large suite, and it is a stylistic bet. A reader who does not know the framework has to learn three prefixes, Establish, Because and It, before the test names make sense, and the framework's own claim of removing noise assumes you have already paid that cost in vocabulary.

## A test is a class with named delegates

The example in the README shows the shape, and it is worth reading closely because most of the framework's behaviour is visible in about twenty lines.

```csharp
using Machine.Specifications;

[Subject("Authentication")]
class When_authenticating_an_admin_user
{
    static SecurityService subject;
    static UserToken user_token;

    Establish context = () =>
        subject = new SecurityService();

    Because of = () =>
        user_token = subject.Authenticate("username", "password");

    It should_indicate_the_users_role = () =>
        user_token.Role.ShouldEqual(Roles.Admin);

    It should_have_a_unique_session_id = () =>
        user_token.SessionId.ShouldNotBeNull();
}
```

The class carries a Subject attribute naming the thing under test, and the class name describes the situation. The static fields hold state, and the three delegate types carry the behaviour. Establish is the arrangement, Because of is the action, and each It is a separate expectation, so a class can assert several things about the same arrangement without repeating the setup.

Because the fields are static and the delegates are instance fields assigned at construction, the arrange step runs before each It, giving each expectation a fresh subject. That is the mechanism behind the framework's isolation, and it is a different model from the constructor-per-test model of xUnit, even though the observable result is similar.

The assertion syntax, ShouldEqual and ShouldNotBeNull, comes from the separate Machine.Specifications.Should package rather than from the core framework, which is why the README lists the assert and mocking libraries as optional. Machine.Fakes is the mocking library in the same family. Keeping the assertions out of the core is a sensible boundary: a team that prefers a different assertion library can use the framework without it.

## Four packages, and the order to install them

The getting started section is short, and it is worth following exactly, because the runner and the SDK are separate packages.

The first step is to create a .NET Framework or .NET Core library project, which is the test project. The second is the framework itself:

```powershell
Install-Package Machine.Specifications
```

The third step installs the test SDK and the Visual Studio runner, and both are needed:

```powershell
Install-Package Microsoft.NET.Test.SDK
Install-Package Machine.Specifications.Runner.VisualStudio
```

The fourth step is optional and covers the assertion and mocking libraries:

```powershell
Install-Package Machine.Specifications.Should
Install-Package Machine.Fakes
```

So the minimum set for a working suite is three packages, and the full set is five. The distinction between the framework package and the runner package is the one to keep in mind, because a project that installs only Machine.Specifications will have specs that never appear in the test explorer.

The packages are published on NuGet under the Machine.Specifications name, and the wiki linked from the repository is the project documentation. The README also points to a PluralSight course on expressive testing with .NET and MSpec, which is a reasonable way to learn the conventions before writing a suite in it.

## The build moved to a newer .NET layout

The repository layout tells you something about where the project is in its life. The root holds an AGENTS.md and a CONTRIBUTING.md, a Directory.Build.props for shared build properties, a GitVersion.yml for versioning, a global.json pinning the SDK, and a build.ps1 alongside build.sh. There is a docs directory, an assets directory, an images directory, a src directory and a tests directory, and the solution file is named Machine.Specifications.slnx.

That last detail is the informative one. The .slnx format is the newer XML-based solution format that Microsoft has been moving Visual Studio towards, and its presence tells you the project has been updated for current tooling rather than being left on the older .sln format.

The build scripts at the root, build.sh and build.ps1, mean the project is built the same way on Unix and on Windows, and the GitVersion configuration means the package versions are derived from the git history rather than typed by hand, which is how a library like this keeps a coherent version line across several packages.

The presence of AGENTS.md is worth noting for contributors. It is a file aimed at coding agents, and it signals that the maintainers expect automated tooling to work on this repository. It is also a small piece of evidence about how the project is maintained, but the source of truth for that is the release history rather than the file.

## What the framework asks you to give up

The honest case against MSpec is about consistency, and it is worth stating plainly rather than treating the framework as universally better.

The first cost is that a spec is not a method with an attribute. It is a class with a name, a Subject attribute, static fields and three kinds of named delegate. That is more structure than a test method, not less, and it only pays off if the reader of the test suite is expected to read it. A team that treats tests as an implementation detail to be run in CI and rarely read by a human gets less from the naming than it pays in unfamiliar syntax.

The second cost is the vocabulary barrier. Establish, Because and It have to be learned, and so do the assertion methods, and the mocking library is a separate API from the widely used alternatives. Anyone joining the team reads a short learning curve before they can write a spec, and anyone reviewing a spec needs to know the conventions to know what the code does.

The third cost is the toolchain commitment. The runner package is specific to MSpec, the assertions are specific to MSpec, and the mocking library is specific to MSpec. A team that has standardised on another framework cannot mix a handful of MSpec specs into an existing suite without carrying two runners, and cannot adopt MSpec incrementally across a large codebase without rewriting tests that already work.

None of that makes the framework wrong. It makes it a choice about reading tests as specifications, which is a legitimate and often correct choice, and one that should be made deliberately rather than by default.

## MIT terms, and a release line that moved after the branch did

The licence is MIT, with a LICENSE file at the root of the repository. For a test framework, that is the uncomplicated case, and there is no commercial restriction on using it in a proprietary codebase.

The release history is the part that deserves a second look. The published versions are v1.1.3 on 2025-05-17, v1.1.2 on 2024-06-23 and v1.1.1 on 2023-03-17, and the last push to the repository was on 2026-10-06. The repository is not archived.

So the branch is being worked on at a steady rate while the published package moves less often, and the most recent published release is already about five months behind the last commit. For a test framework that is a smaller problem than it would be for a runtime library, since a stale test runner rarely breaks a production system, but it does mean the version in your packages.config or csproj tells you less about the code than you might assume.

The build tooling tells you the same thing from the other side. The solution has moved to the .slnx format and the versioning is handled by GitVersion, both of which are choices a project makes when it intends to keep building cleanly against current .NET tooling. A framework that is going to be abandoned tends to stop being updated at this level of detail.

The practical advice for an adopter is to check the wiki and the issue tracker rather than assuming the packaged version reflects the current state of the work, and to read the documentation in the repository's docs directory before deciding whether the grammar suits the way your team writes tests.

## Conclusion

Machine.Specifications suits a .NET team that finds attribute-on-method tests too noisy to read and wants the test name to state the behaviour in full, in the style of a specification. It is the wrong tool for a team that wants its tests to look like every other .NET test suite, because the grammar, the runner and the assertion library are all specific to MSpec, and it is the wrong tool for a project already standardised on a different framework with a large existing suite. Before you adopt it, read the wiki linked from the repository, check that your build can run the Visual Studio runner package alongside the test SDK, and look at the version history, because the latest published release is v1.1.3 from 2025-05-17 while the last push to the branch was on 2026-10-06.

## FAQ

### What is Machine.Specifications and how does it differ from other .NET test frameworks?

MSpec is a context and specification test framework. Instead of the traditional attribute-on-method model it uses custom delegates with anonymous methods, named Establish, Because and It, and the class itself describes the situation under test.

### Which NuGet packages do I need to run Machine.Specifications tests?

At minimum Machine.Specifications, Microsoft.NET.Test.Sdk and Machine.Specifications.Runner.VisualStudio. The assertion library Machine.Specifications.Should and the mocking library Machine.Fakes are optional, and the README lists the install commands for all of them.

### Do I need the Machine.Specifications.Should package?

No, it is optional. It provides the assertion methods such as ShouldEqual and ShouldNotBeNull used in the example, and the README lists it under the optional assert and mocking libraries alongside Machine.Fakes.

### Where is the documentation for Machine.Specifications?

In the project wiki linked from the repository. The README also links a PluralSight course on expressive testing with .NET and MSpec for learning the conventions, and points to GitHub Discussions for questions.

## Sources

- [Issues](https://github.com/machine/machine.specifications/issues)
- [License: MIT](https://github.com/machine/machine.specifications/blob/master/LICENSE)
- [machine/machine.specifications on GitHub](https://github.com/machine/machine.specifications)
- [README](https://github.com/machine/machine.specifications/blob/master/README.md)
- [Releases](https://github.com/machine/machine.specifications/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/machine-machine-specifications
