Home > OS >  Generic base class constructor
Generic base class constructor

Time:05-31

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.

CodePudding user response:

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))
    {
    }
}

CodePudding user response:

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

CodePudding user response:

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);
  • Related