Reputation: 1323
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
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