DrillSergeant 0.0.5-alpha

This is a prerelease version of DrillSergeant.
There is a newer version of this package available.
See the version list below for details.
dotnet add package DrillSergeant --version 0.0.5-alpha
NuGet\Install-Package DrillSergeant -Version 0.0.5-alpha
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DrillSergeant" Version="0.0.5-alpha" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add DrillSergeant --version 0.0.5-alpha
#r "nuget: DrillSergeant, 0.0.5-alpha"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install DrillSergeant as a Cake Addin
#addin nuget:?package=DrillSergeant&version=0.0.5-alpha&prerelease

// Install DrillSergeant as a Cake Tool
#tool nuget:?package=DrillSergeant&version=0.0.5-alpha&prerelease

DrillSergeant

.net behavior driven testing written by developers, for developers.

Summary

DrillSergeant is a behavior testing library that empowers developers to apply BDD practices with minimal amount of friction. Simply import the package and write your behaviors in familiar C# syntax.

Getting Started

For a complete example of a feature, see this

Creating a Behavior

Creating a behavior is very simple:

[Behavior, Theory, InputData(1,1)]
public Behavior MyBehaviorTest(int value1, int value2)
{
    var input = new Input(value1, value2);
    var behavior = new Behavior<Context,Input>(input);

    // Configure behavior here...
    
    return behavior;
}

Behaviors are regular test methods that are decorated with the [Behavior] attribute and return an instance of a Behavior class. Because [Behavior] is built on top of [Theory] one or more inputs must be provided to the test. This can be done through any xunit data discovery mechanism (e.g. [InlineData], [MemberData], etc...).

Context and Input

DrillSergeant is built on top of xunit and makes use of [Theory] based tests. As a result behavior tests require both a context to hold state throughout the test and input to drive the test. These are typically defined using the C# record type:

public class Context {};
public record Input();

Each step within a behavior can update its context, which is then fed into the next step. It's recommended to use the C# record type for inputs and class for context.

Configuring Input and Context

The only required parameter to a behavior is the input parameter, which must be a type of TInput. Context on the other hand is optional and can be omitted. If it is, then a new instance of TContext will be instantiated using its parameterless constructor.

Use the WithInput() and WithContext() methods to configure the behavior:

var input = new Input();
var behavior1 = new Behavior<Context,Input>(input); // Creates context automatically.
var behavior2 = new Behavior<Context,Input>(input, new Context()); // Manually specify context.

Configuring Steps

Individual steps can be configured depending on the level of granularity required.

Inline Steps

Inline steps are the simplest type of step. An inline step can be added simply by calling Given()/When()/Then() and passing in a lambda:

Given("My step", (c,i) => {
    // Perform some action
});

All steps pass the context and input as the first two parameters. To pass additional dependencies, can call one of the generic overrides:

Given<MyDependency>("My step", (c,i, dep) => {
    // Perform some action
});

Inline steps are convenient when you need a one-off step that won't be reused in other behaviors.

Lambda Steps

Lambda steps are ideal for situations where a step needs to be reused for multiple behaviors within a single class:

public LambdaStep<Context,Input> MyStep =>
    new GivenLambdaStep<Context,Input>()
        .Named("My step")
        .Handle( (c,i) => {
		    // Perform some action.
    });

As you can see, the syntax is nearly identical to an inline step. In fact, inline steps are actually converted to lambda steps behind the scenes.

Class Steps

Class steps are the most flexible type of step and best used when a particular step needs to be reused between multiple features. To create a class step, override the desired verb and fill in the step method:

public class MyStep<Context,Input> : GivenStep<Context,Input>
{
    public override void Given(Context context, Input input)
    {
        // Perform some action.
        return context with { /* changes */ };
    }
}

Unlike inline and lambda steps, class steps are convention based. By default, The GivenStep, WhenStep, and ThenStep provide virtual methods for convenience, but it is not required to use them. Internally, DrillSergeant will pick a matching verb method with the most parameters. For example:

public class MyStep<Context,Input> : GivenStep<Context,Input>
{
    // DrillSergeant will *not* excute this.
    public override void Given(Context context, Input input)
    {
        // Perform some action.
    }
  
    // DrillSergeant will execute this.
    public override void Given(Context context, Input input, MyDependency dependency)
    {
        // Perform some action.
    }
}

Configuring the Resolver

DrillSergeant has first-class support for dependency injection. When writing a behavior method, simply prefix any dependency with the [Inject] attribute:

[Behavior]
public Behavior MyBehavior([Inject] MyDependency dependency)
{
    // ...
}

The [Inject] parameter is needed so that DrillSergeant can differentiate between input parameters passed by [Theory] and what it needs to inject.

Dependency resolution is handled with the IDependencyResolver interface, which contains a single method: object Resolve(Type type). By default DrillSergeant will satisfy dependencies by instantiating new instances of them via the Activator.CreateInstance() method. To override this: a custom resolver can be configured in the behavior class:

[BehaviorResolverSetup]
public IDependencyResolver SetupResolver()
{
    var resolver = A.Fake<IDependencyResolver>();
  
    A.CallTo(() => resolver.Resove(typeof(MyDependency))).Returns(new MyDependency);
}

In this example, we're using the mocking library FakeItEasy to create a resolver that returns instances of the required dependency, but for more advanced scenarios a real DI container can be substituted in its place.

Note: The resolver is scoped to each test case and does not share data between tests. To share data, use xunit's ClassFixture and CollectionFixture fixtures. **Note: The name of the method here is unimportant. DrillSergeant will look for the first public method returning an IDependencyResolver that is marked with the [BehaviorSetupResolver] attribute.c

Best Practices

Keep Logic In Behaviors to a Minimum

Logic for behaviors should go in their respective steps. Likewise setup code for dependency resolution should be taken care of within the [BehaviorResolverSetup] method. Try to avoid writing any code within the behavior itself unless it is trivial.

Internally DrillSergeant will execute the behavior method prior to executing any test cases, therefore it's important not to write any logic within the behavior itself. The behavior should only be a single a single return statement.

public Behavior MyBehavior(int a, int b, [Inject] MyDependency dependency)
{
    var input = new Input(a,b); // This is ok.
    dependency.Initialize(); // Put this in the resolver setup method.
    
    return new Behavior<Context,Input>(input);
}

Favor Xunit Class/Collection Fixtures

Xunit already has a mechanism for handling injection of dependencies with IClassFixture<> and ICollectionFixture. These should be preferred by default. The IDependencyResolver is experimental and may be removed before the official release.

Product 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on DrillSergeant:

Package Downloads
DrillSergeant.MSTest

Write behavior tests in pure C#.

DrillSergeant.NUnit3

Write behavior tests in pure C#.

DrillSergeant.Xunit2

Write behavior tests in pure C#.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.2.2 44 4/21/2024
1.2.1 34 4/21/2024
1.2.0 55 2/20/2024
1.2.0-alpha.40 71 1/20/2024
1.2.0-alpha.39 46 1/20/2024
1.2.0-alpha.38 47 1/20/2024
1.2.0-alpha.37 44 1/20/2024
1.2.0-alpha.35 128 11/19/2023
1.2.0-alpha.34 53 11/19/2023
1.2.0-alpha.33 57 11/13/2023
1.1.8 41 2/20/2024
1.1.2 72 1/20/2024
1.1.1 217 11/12/2023
1.1.0-alpha.42 57 11/12/2023
1.1.0-alpha.41 53 11/12/2023
1.1.0-alpha.39 49 11/12/2023
1.1.0-alpha.38 55 11/12/2023
1.1.0-alpha.37 56 11/12/2023
1.1.0-alpha.35 55 11/12/2023
1.0.3 160 10/21/2023
1.0.1 141 10/12/2023
1.0.0-beta.53 68 9/30/2023
1.0.0-beta.52 58 9/29/2023
0.6.2 157 8/20/2023
0.6.1-beta 111 8/20/2023
0.6.0-beta 111 8/20/2023
0.5.0 167 7/20/2023
0.4.0 167 7/16/2023
0.3.0-beta 141 7/12/2023
0.2.0-beta 141 7/9/2023
0.1.0-beta 78 7/4/2023
0.0.17-alpha 97 7/3/2023
0.0.16-alpha 93 6/30/2023
0.0.15-alpha 105 6/29/2023
0.0.14-alpha 97 6/23/2023
0.0.13-alpha 90 6/23/2023
0.0.12-alpha 91 6/16/2023
0.0.11-alpha 93 6/14/2023
0.0.10-alpha 93 6/10/2023
0.0.9-alpha 91 5/28/2023
0.0.8-alpha 90 5/25/2023
0.0.7-alpha 86 5/23/2023
0.0.6-alpha 89 5/20/2023
0.0.5-alpha 97 5/20/2023
0.0.4-alpha 91 5/17/2023
0.0.3-alpha 83 5/14/2023
0.0.2-alpha 89 5/12/2023