Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

xUnit.net: Test class constructors not being run?

I'm trying to run some setup code in one of my xUnit.net test classes, but although the tests are running it doesn't appear the constructor is.

Here's some of my code:

public abstract class LeaseTests<T>
{
    private static readonly object s_lock = new object();
    private static IEnumerable<T> s_sampleValues = Array.Empty<T>();

    private static void AssignToSampleValues(Func<IEnumerable<T>, IEnumerable<T>> func)
    {
        lock (s_lock)
        {
            s_sampleValues = func(s_sampleValues);
        }
    }

    public LeaseTests()
    {
        AssignToSampleValues(s => s.Concat(CreateSampleValues()));
    }

    public static IEnumerable<object[]> SampleValues()
    {
        foreach (T value in s_sampleValues)
        {
            yield return new object[] { value };
        }
    }

    protected abstract IEnumerable<T> CreateSampleValues();
}

// Specialize the test class for different types
public class IntLeaseTests : LeaseTests<int>
{
    protected override IEnumerable<int> CreateSampleValues()
    {
        yield return 3;
        yield return 0;
        yield return int.MaxValue;
        yield return int.MinValue;
    }
}

I'm using the SampleValues as a MemberData, so I can use them in tests like this

[Theory]
[MemberData(nameof(SampleValues))]
public void ItemShouldBeSameAsPassedInFromConstructor(T value)
{
    var lease = CreateLease(value);
    Assert.Equal(value, lease.Item);
}

However, I'm consistently getting an error saying that "no data was found for [method]", for all the methods that use SampleValues. After I investigated further, I found out the LeaseTests constructor wasn't even being run; when I set a breakpoint on the call to AssignToSampleValues, it wasn't hit.

Why is this happening, and what can I do to fix it? Thanks.

like image 275
James Ko Avatar asked Jun 18 '16 18:06

James Ko


1 Answers

The constructor is not running because MemberData is evaluated before creating instance of specific test class. I'm not sure if this will fulfill your requirements, but you can do the following:

Define ISampleDataProvider interface

public interface ISampleDataProvider<T>
{
    IEnumerable<int> CreateSampleValues();
}

Add type specific implementation:

public class IntSampleDataProvider : ISampleDataProvider<int>
{
    public IEnumerable<int> CreateSampleValues()
    {
        yield return 3;
        yield return 0;
        yield return int.MaxValue;
        yield return int.MinValue;
    }
}

Resolve and use data provider in SampleValues method

public abstract class LeaseTests<T>
{
    public static IEnumerable<object[]> SampleValues()
    {
        var targetType = typeof (ISampleDataProvider<>).MakeGenericType(typeof (T));

        var sampleDataProviderType = Assembly.GetAssembly(typeof (ISampleDataProvider<>))
                                                .GetTypes()
                                                .FirstOrDefault(t => t.IsClass && targetType.IsAssignableFrom(t));

        if (sampleDataProviderType == null)
        {
            throw new NotSupportedException();
        }

        var sampleDataProvider = (ISampleDataProvider<T>)Activator.CreateInstance(sampleDataProviderType);
        return sampleDataProvider.CreateSampleValues().Select(value => new object[] {value});
    }
...
like image 165
Aleksey L. Avatar answered Oct 17 '22 04:10

Aleksey L.