Adam G
Adam G

Reputation: 1323

Issue trying to combine class type constraint with type constraint for specific interface

I am trying to write unit tests for a number of "engines". Each engine type has distinct "configurations" but there is significant overlap in how they are instantiated, other arguments, and how the mocks all need to hang together. To solve this, I have a generic test helper class but I am struggling to get the type constraints working how I need.

Following is a simplification that hopefully illustrates the issue:

//using Moq;

public interface IGetFooableConfig
{
    int GetFoo();
    // + other things out of scope for this question
}

public interface IFoo1Config : IGetFooableConfig 
{
    // + other things out of scope for this question
}

public class Foo1Config : IFoo1Config
{
    public int GetFoo() { return 1; }
    // + other things out of scope for this question
}

public interface IFoo2Config : IGetFooableConfig
{
    // + other things out of scope for this question
}

public class Foo2Config : IFoo2Config
{
    public int GetFoo() { return 2; }
    // + other things out of scope for this question
}

public abstract class TestBuilder<GetFooableConfigInterface>
    where GetFooableConfigInterface : IGetFooableConfig, class
{
    Mock<GetFooableConfigInterface> configMock = new Mock<GetFooableConfigInterface>();

    public void Bar()
    {
        this.configMock.Setup(s => s.GetFoo()).Returns(1);
        // + other things out of scope for this question
    }

    // + other things out of scope for this question
}

public class Foo1Builder : TestBuilder<IFoo1Config>
{
    // + other things out of scope for this question
}

public class Foo2Builder : TestBuilder<IFoo2Config>
{
    // + other things out of scope for this question
}

The Foo1Engine tests then use Foo1Builder, Foo2Engine tests use Foo2Builder (etc x lots)

The issue I am having with the above code is that

public abstract class TestBuilder<GetFooableConfigInterface>
    where GetFooableConfigInterface : IGetFooableConfig, class

... causes the error

The 'class', 'struct', 'unmanaged', and 'notnull' constraints cannot be combined or duplicated, and must be specified first in the constraints list

If I instead change this to:

public abstract class TestBuilder<GetFooableConfigInterface>
    where GetFooableConfigInterface : IGetFooableConfig

I get a different issue with

    Mock<GetFooableConfigInterface> configMock = new Mock<GetFooableConfigInterface>();

... that causes the error

The type 'GetFooableConfigInterface' must be a reference type in order to use it as parameter 'T' in the generic type or method 'Mock

This is because Moq declares it as public class Mock<T> : Mock, IMock<T> where T : class

If I instead change it to:

public abstract class TestBuilder<GetFooableConfigInterface>
    where GetFooableConfigInterface : class

This fixes both the above errors but introduces a different issue with

this.configMock.Setup(s => s.GetFoo()).Returns(1);

... that causes the error

'GetFooableConfigInterface' does not contain a definition for 'Foo' and no accessible extension method 'Foo' accepting a first argument of type 'GetFooableConfigInterface' could be found (are you missing a using directive or an assembly reference?)

How can I convince the compiler that GetFooableConfigInterface should be both a class and also an implementer of IGetFooableConfig?

Thanks

Upvotes: 0

Views: 480

Answers (1)

Sweeper
Sweeper

Reputation: 271050

The error message says:

The 'class' ... constraint ... must be specified first in the constraints list

So you should specify it in this order:

public abstract class TestBuilder<GetFooableConfigInterface>
    where GetFooableConfigInterface : class, IGetFooableConfig
{

For why this order is imposed, see Why is some ordering enforced in generic parameter constraints?

Upvotes: 1

Related Questions