Skip to content

Unit Tests Implementation

devdigital edited this page Nov 4, 2016 · 18 revisions

Postfix your Unit Test Project Names with .UnitTests

You should postfix any unit test project name with .UnitTests

Why?

Using an explicit .UnitTests name at the end of your unit test Visual Studio projects/assemblies ensures that developers know the appropriate location for their quick running unit tests.

It also allows you to easily identify the unit test projects to execute during a commit build. If test projects contained a mixture of unit and integration tests, it would be harder to differentiate between them to determine which to run on commit builds, and which tests to only run during a nightly build (as they take longer to run).

Unit test projects are also likely to have different dependencies to integration or acceptance tests, therefore having separate projects per test type ensures that you're reducing the required project dependencies.

Setup a New Unit Test Project as a Class Library

TODO

Install the AutoFixture.Xunit2, AutoFixture.AutoMoq, and xunit.assert NuGet Packages

The fastest way to get started with unit testing with a new test project is to install just three NuGet packages - AutoFixture.Xunit2, AutoFixture.AutoMoq, and xunit.assert. Installing these three packages also installs all of the required and recommended test and isolation frameworks (XUnit.net 2, Moq, AutoFixture).

Note that the AutoFixture.Xunit2 NuGet package also installs 
xUnit.net 2 as a (recommended) test framework

xUnit.net is the recommended test framework because of its extensibility and compatibility with AutoFixture.

Note that the AutoFixture.AutoMoq package also installs 
Moq as a (recommended) isolation framework

Moq is the recommended isolation framework because of its fluent interface and compatibility with AutoFixture.

Put Your Tests in a Tests folder

TODO

Use Fact for Tests with No Parameters

[Fact]
public void Test()
{
  ...
}

Use Theory and InlineData for Simple Parameterised Tests

[Theory]
[InlineData(4)]           // will pass
[InlineData(5)]           // will fail
public void Test(int i)
{
    Assert.Equal(4, i);
}

Use Theory, AutoData for Tests with Parameters Created By AutoFixture

Note that the `AutoData` attribute is in the `Ploeh.AutoFixture.Xunit` namespace

Use Theory, AutoData for Tests with Mock Parameters

[Theory, AutoData]
public void Test(Mock<IMyInterface> param)
{
   ...
}

Simplify Injecting Mock Parameters with a Custom AutoDataMoq Attribute

The above test can be simplified by creating a custom AutoDataMoq attribute that uses the AutoFixture AutoMoqCustomization. This fixture customization is described as performing the following:

 If a type falls through the normal engine without being handled, the auto-mocking 
 extension will check whether it is a request for an interface or abstract class. 
 If this is so, it will relay the request to a request for a `Mock` of the same type.

The attribute does not come as part of the AutoFixture.AutoMoq NuGet package, so it does need implementing for each new product:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture()
            .Customize(new AutoMoqCustomization()))
    {
    }
}

Note that AutoMoqCustomization is defined in the Ploeh.AutoFixture.AutoMoq namespace

This means that the above unit test can be simplified to:

[Theory, AutoMoqData]
public void Test(IMyInterface param)
{
   // param here will be a Moq proxy
}

Use Theory, InlineAutoData to Have AutoFixture Generate Values for Unspecified Parameters

[Theory]
[InlineAutoData(4, 5)]
public void Test(int x, int y, int z)
{
   Assert.Equal(4, x);   // passes
   Assert.Equal(5, y);   // passes
   // The value for z will be auto-generated by AutoFixture
}
Note that the order of test parameters is important here, there is no way of not 
specifying early parameters whilst specifying the values of later parameters.

Use a custom attribute to inject known instances

If you wish to share a known instance of a type across many test methods, one option is to inject a factory into your test method:

public void Test(MyTypeFactory myTypeFactory)
{
  var sut = myTypeFactory.Create();
  ...
}

However, you may prefer to inject the type directly into your test method, rather than explicitly invoking the factory. You may also need to access other (frozen) types

To allow for this, you can create a custom attribute and a factory base type:

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public class CustomizeWithAttribute : CustomizeAttribute
{
    private readonly Type type;

    public CustomizeWithAttribute(Type type)
    {
        if (type == null)
        {
            throw new ArgumentNullException(nameof(type));
        }

        if (!typeof(ICustomization).IsAssignableFrom(type))
        {
            throw new InvalidOperationException($"Type must implement {typeof(ICustomization).Name}");
        }

        this.type = type;
    }

    public override ICustomization GetCustomization(ParameterInfo parameter)
    {
        return Activator.CreateInstance(this.type) as ICustomization;            
    }
}

public abstract class InstanceFactory<T> : ICustomization
{
    private IFixture fixture;

    public void Customize(IFixture fixture)
    {
        if (fixture == null)
        {
            throw new ArgumentNullException(nameof(fixture));
        }

        this.fixture = fixture;
        this.fixture.Register(this.CreateInstance);
    }

    public abstract T CreateInstance();

    protected TType CreateType<TType>()
    {
        return this.fixture.Create<TType>();
    }
}

With these in place, you can create a factory for your type:

internal class MyTypeFactory : InstanceFactory<MyType>
{
    public override MyType CreateInstance()
    {
        return new MyType(...);
    }
}

And you can use your factory with the CustomizeWithAttribute in your test method:

[Theory] 
[AutoData]       
public void Test([CustomizeWith(typeof(MyTypeFactory))]MyType myType)
{
   ...
}

If you need to access instances of other types injected into your test method from your factory, then you can use the provided CreateType<TType> method:

internal class MyTypeFactory : InstanceFactory<MyType>
{
    public override MyType CreateInstance()
    {
        return new MyType(this.CreateType<MyOtherType>());
    }
}

And your test becomes:

[Theory] 
[AutoData]       
public void Test([Frozen]MyOtherType myOtherType, [CustomizeWith(typeof(MyTypeFactory))]MyType myType)
{
   // myType will be constructed with myOtherType instance
}

You can simplify your test method even further by creating your own derived attribute for your specific injected type:

public class MyTypeFactoryAttribute : CustomizeWithAttribute
{
    public MyTypeFactoryAttribute() : base(typeof(MyTypeFactory))
    {
    }
}

And your test becomes:

[Theory] 
[AutoData]       
public void Test([Frozen]MyOtherType myOtherType, [MyTypeFactory]MyType myType)
{
   // myType will be constructed with myOtherType instance
}