1

Using this generic base class:

public abstract class Logic<U> where U : class
{
    protected U m_provider;
    public Logic(U provider)
    {
        m_provider = provider;
    }
}

I'm trying to create a base test class for unit test:

public class LogicBaseTest<T, U> where T : Logic <U>, new()  where U: class
{
    protected T m_logic;
    protected U m_provider;

    [OneTimeSetUp]
    public virtual void OneTimeSetup()
    {
        m_provider = (U)Substitute.For<IInterface>();

        m_logic = new T(m_provider);
    }
}

It complains on the constructor, it requests for the new() constrain but when I add it then it complains that the constructor cannot take parameters.

I could add a method to populate the provider but I'm wondering whether it could be done in the constructor.

4
  • 3
    The problem is that with where T : Logic<U>, new() you're saying "So, whatever type T is, it must inherit from Logic and the generic parameter must be U. Also, T needs to have a public parameterless constructor" but your Logic<U> class alone already violates the new() constraint, the solution is either remove the new() constraint or add a public parameterless constructor to Logic Commented May 31, 2022 at 7:15
  • I added the new() constraint because it complained that "Cannot create an instance of the variable 'T' because it does not have the new() constraint." Commented May 31, 2022 at 7:21
  • I'm wondering how you would ever instantiate that test-class. Which test-framework are you using? Commented May 31, 2022 at 7:23
  • Using NUnit and NSubstitute. I am able to get it working by not calling the generic ctor and instead calling in the sub class with the actual type. But I'm wondering whether it would be possible to get that generic ctor to work. Commented May 31, 2022 at 7:29

3 Answers 3

5

So you have two problems here:

  1. LogicBaseTest needs to know how to instantiate a Logic<U>.
  2. Logic<U> requires a U in the constructor.

My proposed solution to it is to pass a factory delegate into the base test class and remove the new() requirement. Then your setup can construct the Logic class using the factory:

public class LogicBaseTest<T, U> 
    where T : Logic<U>
    where U: class
{

    protected readonly Func<U, T> _factory;

    public LogicBaseTest(Func<U, T> factory)
    {
        _factory = factory;
    }

    [OneTimeSetUp]
    public virtual void OneTimeSetup()
    {
        m_provider = (U)Substitute.For<IInterface>();
        m_logic = _factory(m_provider);
    }
}

In the derived test class you just have to tell the base class how to new up a Logic<U>:

public class DerivedTest : LogicBaseTest<Logic<MyUType>, MyUType>
{
    public DerivedTest()
        : this(u => new Logic<MyUType>(u))
    {
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

I'll go with the factory, it looks somehow similar to my last comment on removing the ctor from base class and calling the subclass ctor from subtest.
2

Let's break down your generic constraint into plain English

where T : Logic<U>, new()

This means

The type of T needs to inherit from Logic, the generic type parameter must be U and have a public, parameterless, constructor

But the problem is that Logic by itself already breaks that constraint. Now, how do we fix this? There are multiple ways

  1. Use a "factory function" to instantiate your m_logic alongside removing the new constraint (see DiplomacyNotWar's answer)

  2. Remove the new() constraint and use something like Activator.CreateInstance instead to instantiate your Logic class

  3. Add a parameterless constructor to Logic and configure your m_provider some other way

  4. Move instantiation of m_logic into the unit test itself (maybe add a helper method if you need to create the same Logic for a ton of unit tests)

  5. Research if your unit testing framework supports some form of Dependency Injection and inject everything you need

Comments

1

You cannot add a generic type constraint such as where T : new(U). Instead, you can use a Factory.

public interface IFactory<out TObject, in TProvider>
{
    public TObject Create(TProvider provider);
}

then use it in your base test

public class LogicBaseTest<T, U> where T : Logic <U> where U: class // remove new()
{
    // fields

    private readonly IFactory<T, U> _factory;

    public LogicBaseTest(IFactory<T, U> factory)
    {
        _factory = factory;
    }

    [OneTimeSetUp]
    public virtual void OneTimeSetup()
    {
        m_provider = (U)Substitute.For<IInterface>();
       
        m_logic = _factory.Create(m_provider);
    }
}

Example

public class Logic1Provider
{
    
}

public class Logic1Factory : IFactory<Logic1, Logic1Provider>
{
    public Logic1 Create(Logic1Provider provider)
    {
        return new Logic1(provider);
    }
}

public class Logic1 : Logic<Logic1Provider>
{
    public Logic1(Logic1Provider provider) : base(provider)
    {
    }

    public void DoDomeLogic()
    {
        // do stuff
    }
}
var factory = new Logic1Factory();
var baseTest = new LogicBaseTest<Logic1, Logic1Provider>(factory);

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.