简体   繁体   中英

C# Unit Testing (Xunit and Moq) - Test an interface and its implementations separately

I want to separate the unit tests for testing the interface behavior itself and the additional business behavior of the implementations.

public interface IIncrementor
{
    void Increment();

    int Count { get; }
}

public class AIncrementor : IIncrementor { /* Implementation */ }
public class BIncrementor : IIncrementor { /* Implementation */ }

To ensure the correctness of the business behavior of an interface I need to check all according implementations against my defined unit tests. Of course every implementation has its own additional behavior to test, but I don't want to repeat myself over and over again.

My current solution was to create an abstract test-class with an abstract property for the subclasses to implement.

public abstract class IIncrementorTest
{
    protected abstract IIncrementor Incrementor { get; }

    [Fact]
    public void WhenIncremented_ThenCounterHasCorrectValue()
    {
        var oldCount = Incrementor.Count;

        Incrementor.Increment();

        Assert.Equal(oldCount + 1, Incrementor.Count);
    }
}

public class AIncremetorTest : IIncrementorTest
{
    protected override IIncrementor Incrementor => new AIncrementor();

    [Fact]
    public void WhenSomeAdditional_ThenCheckSomething() { /* More test */ }
}

public class BIncremetorTest : IIncrementorTest
{
    protected override IIncrementor Incrementor => new BIncrementor();

    [Fact]
    public void WhenSomeAdditional_ThenCheckSomething() { /* More test */ }
}

But that leads to some problems, eg when a test case needs to build an instance differently which is very likely to happen. Would you suggest to create abstract properties for these cases?

protected abstract IIncrementor IncrementorForCheckingSomething { get; }

Or is there a best practice approach which solves this interface-test problem comprehensively?

If I'm understanding correctly, you want to write one set of tests for your interface, and apply those tests to any classes which implement that interface.

You could use reflection to find all the types in your assembly which implement the interface with something like this:

var interfaceType = typeof(IIncrementor);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => t.IsClass && interfaceType.IsAssignableFrom(t));

and instantiate all of those types. This could be tricky if they don't share a common constructor signature; but would work for the examples in the question using the default constructor...

foreach(Type t in types)
{
    yield return (IIncrementor)Activator.CreateInstance(t,null);
}

Put that code in a method with return type IEnumerable<IIncrementor> and give that method the attribute MemberData to pass these instances into each of the tests.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM