reusing the same tests for different implementation
Asked Answered
W

2

10

Using xUnit.net what would be a clean (readable/understandable and maintainable) way to reuse the same test for multiple implementations of the same interface?

The act and assert part of my tests are always the same (because all implementations of the interface are supposed to behave the same). Just the SUT is different for each test run and for some particular tests the arrange part is slightly different.

For example I have multiple implementations (MemoryRepository, FileReposity ...) of the following interface.

interface IRepository
{
    object GetById(string id);
    void Set(string id, object value);
}

Now my tests are supposed to assure that all implementations behave the same:

// all implementations of IRepository must behave like this
// so do this test for all implementations
[Fact]
public void GetRetrievesWhatWasPut()
{
    IRepository sut = new MemoryRepository();
    sut.Set("key", 10);
    var result = sut.Get("key");
    result.Should().Be(10);
}
Whacky answered 4/3, 2014 at 9:37 Comment(0)
A
7

You could consider writing test classes as generic classes:

public abstract class IntervalFacts<T>
{
    [Theory, AutoCatalogData]
    public void MinimumIsCorrect(IComparable<T> first, 
        IComparable<T> second)
    {
        var sut = new Interval<T>(first, second);
        IComparable<T> result = sut.Minimum;
        Assert.Equal(result, first);
    }
}

public class DecimalIntervalFacts : IntervalFacts<decimal> { }
public class StringIntervalFacts : IntervalFacts<string> { }
public class DateTimeIntervalFacts : IntervalFacts<DateTime> { }
public class TimSpanIntervalFacts : IntervalFacts<TimeSpan> { }

This particular example takes advantage of AutoFixture's ability to compose concrete classes, which saves you the trouble of instantiating concrete instances of each T.

Varying the arrange phase is more difficult, but depending on what you need to do, again, you may be able to introduce some conventions to AutoFixture so that it automatically varies the created instances based on type.


For the types in the OP, you could write the tests like this:

public abstract class RepositoryFacts<T> where T : IRepository
{
    [Theory, AutoRepositoryData]
    public void GetRetrievesWhatWasPut(T sut)
    {
        sut.Set("key", 10);
        var result = sut.Get("key");
        result.Should().Be(10);
    }        
}

public class MemoryRepositoryFacts : RepositoryFacts<MemoryRepository> { }
public class FileReposityRepositoryFacts : RepositoryFacts<FileReposity> { }
Alive answered 4/3, 2014 at 10:11 Comment(4)
I don't quite see how using generic test classes help me to vary the SUT. Your example does not vary the SUT.Whacky
I think there might be some sort of misunderstanding, I have updated my question with an example. I see how I could use [Theory] and a DataAttribute to data-drive the SUT but I don't see a benefit of using generics.Whacky
OK I get it, the reason you use generic classes so your AutoFixture can automagically create the correct SUTs for me. But I could also just use the PropertyDataAttribute and create the SUTs myself.Whacky
The reason I use generics is also because it provides compile-time safety, which a PropertyDataAttribute will not.Alive
G
1

You can test multiple implements by having a abstract method like GetThingToBeTested()

    public abstract class Tester
    {
        public abstract Func<string, string, bool> GetThingToBeTested();

        [Theory]
        [InlineData("carrot", "tarroc")]
        [InlineData("apple", "papel")]
        public void Will_be_a_permutation(string original, string valueToTest)
        {
            GetThingToBeTested()(original, valueToTest).Should().BeTrue();
        }

        [Theory]
        [InlineData("hello", "llloh")]
        public void Will_not_be_a_permutation(string original, string valueToTest)
        {
            GetThingToBeTested()(original, valueToTest).Should().BeFalse();
        }
    }

    public class Sort : Tester
    {
        public override Func<string, string, bool> GetThingToBeTested()
        {
            return IsPermutation_Sort;
        }
    }

    public class Count : Tester
    {
        public override Func<string, string, bool> GetThingToBeTested()
        {
            return IsPermutation_Count;
        }
    }
Gorges answered 30/7, 2020 at 23:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.