0

stupid question possibly, first time I'm actually writing a unit test at all (shame on me). I'm using Xunit and AutoFixture. So, I have some unit tests that I would like to run with different combinations of input data.

For example, say I'm testing my DAL and want to test the same unit test with different types of repositories (e.g. in-memory and SQL). I also need to have some other data created, which will be used both by the unit test as well as the repository. Some of my classes need to be created via factory methods, as they don't have any public constructors.

My ideal would be do to something like

[Theory, InMemoryRepository, SomeOtherData]
[Theory, SqlRepository, SomeOtherData]
// ... some other data combinations ...
public void SomeTest(IRepository repository, ISomeOtherData someOtherData)
{
    // do some tests here
}

public class InMemoryRepositoryAttribute : AutoDataAttribute
    {

    public InMemoryRepositoryAttribute()
    {
        Fixture.Register<IRepository>(CreateRepository);
    }

    protected IRepository CreateRepository()
    {
        var data = Fixture.CreateMany<ISomeOtherData>().ToList();  // this will throw an exception "AutoFixture was unable to create an instance (...)" as my binding doesn't seem be available in the Fixture
        var repository = new InMemoryDAL.Repository();
        repository.Insert(data);

        return repository;
    }
}

public class SomeOtherDataAttribute : AutoDataAttribute
{
    public SomeOtherDataAttribut()
    {
        Fixture.Register<ISomeOtherData>(CreateData);
    }

    protected ISomeOtherData CreateData()
    {
        string test = Fixture.Create<string>();
        var data = (new SomeOtherDataFactory()).Create(test);
        return data;
    }
}

but this doesn't work, as both my AutoDataAttribute-classes seem to be based on seperate fixtures. I.e. whatever I register in my SomeOtherDataAttribute doesn't seem to be available in my InMemoryRepositoryAttribute instance.

Is there any way to fix this & use attributes to combine different sets of data in a Fixture?

Or what alternatives would you suggest - maybe best to create individual test functions for each data combination, and call SomeTest() explicitly from there?

Thanks!

Bogey
  • 4,926
  • 4
  • 32
  • 57

1 Answers1

4

Actually, you can achieve this by defining a custom AutoDataAttribute that allows supply array of customizations types. Here is a example of how to accomplish this task:

public class CompositeCustomizationsAttribute : AutoDataAttribute
{
    public CompositeCustomizationsAttribute(params Type[] customizationTypes)
    {
        foreach (var type in customizationTypes)
        {
            var customization = (ICustomization)Activator.CreateInstance(type);
            Fixture.Customize(customization);
        }
    }
}

public class InMemoryRepositoryCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Register<IRepository>(() => CreateRepository(fixture));
    }

    protected IRepository CreateRepository()
    {
        var data = Fixture.CreateMany<ISomeOtherData>().ToList();
        var repository = new InMemoryDAL.Repository();
        repository.Insert(data);

        return repository;
    }
}

public class SomeOtherDataCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Register<ISomeOtherData>(() => CreateData(fixture));
    }

    protected ISomeOtherData CreateData()
    {
        string test = Fixture.Create<string>();
        var data = (new SomeOtherDataFactory()).Create(test);
        return data;
    }
}

Given above customizations and CompositeCustomizationsAttribute you can now define your test method :

[Theory, CompositeCustomizations(typeof(InMemoryRepositoryCustomization), typeof(SomeOtherDataCustomization))]
public void SomeTest(IRepository repository, ISomeOtherData someOtherData)
{
    // do some tests here
}
Arkadiusz K
  • 1,797
  • 1
  • 17
  • 18
  • Hi, thanks for your reply. Only downside is that you can't specify any parameters/constructor arguments to these types easily (unless maybe going for some slightly more ugly code that has type followed by object array followed by type followed by...), but guess that's rather a limitation of attributes as such. Will probably go with this solution anyway. Thanks! – Bogey Sep 15 '15 at 08:23
  • 1
    Yes you're right. This is a some kind of trade-off. And actually in software development we very often must deal with trade-offs. – Arkadiusz K Sep 15 '15 at 09:33