1

I want to create generic Unit Test classes for Services. My base class is as generic as possible and I wanna pass the Service constructor as a parameter to my base class from the derived class, but I don't no how to do it in C#.

public interface IBaseServiceUnitTest<TEntity> where TEntity : BaseEntity
{
    //...some methods
}

public class BaseServiceUnitTest<TEntity> : IBaseServiceUnitTest<TEntity> where TEntity : BaseEntity
{
    private IBaseService<TEntity> _service;

    public BaseServiceUnitTest(Constructor ctor)
    {
        _service = ctor();
    }

    //...implemented methods from IBaseServiceUnitTest
}

public class CustomEntityServiceUnitTest : BaseServiceUnitTest<CustomEntity>
{
    public CustomEntityServiceUnitTest() 
        : base(Constructor ctor)
}
Felipe Endlich
  • 540
  • 5
  • 24
  • 2
    Why do you want to pass in a constructor method and not just the `IBaseService` object? – DavidG Nov 27 '19 at 23:03
  • Do you mean pass the class object? – Amr Elgarhy Nov 27 '19 at 23:03
  • Pretty sure you can not. Constructors are a very special function. The closest equivalent is creating instances at runtime, and thus handing in the type of class. – Christopher Nov 27 '19 at 23:03
  • 3
    This might be an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). It would be awesome if we could get a clearer picture of the current problem and what you are **actually** trying to do? – Nkosi Nov 27 '19 at 23:24
  • @DavidG because the IBaseService has some arguments in its constructor and I wanna do this logic inside the BaseServiceUnitTest – Felipe Endlich Nov 27 '19 at 23:57
  • @Nkosi I told you...I wanna create a generic BaseServiceUnitTest class that tests all basic service methods in my project – Felipe Endlich Nov 27 '19 at 23:58
  • @FelipeEndlich There was no mention about having to pass arguments in the original explanation. [edit] the question and give an example of the arguments – Nkosi Nov 28 '19 at 00:01
  • So, even if you could pass in a constructor, how do you think you would call it with an unknown number of parameters? – DavidG Nov 28 '19 at 00:07
  • @DavidG It's not unknown because all my services inherits from a baseService class, so they all have the same constructor. The thing is, I'm doing that and not passing the instance itself because I have to mock some interfaces before constructing the Services instances itself, and the best place to do that is inside the BaseServiceUnitTest – Felipe Endlich Nov 28 '19 at 00:36
  • 1
    If it is known, then just pass in the service then. – DavidG Nov 28 '19 at 00:41
  • The point is, I wanna do this class as generic as possible, in order to don't repeat code in the services – Felipe Endlich Nov 28 '19 at 00:56
  • The class is still generic, I don't understand why you need to complicate it. – DavidG Nov 28 '19 at 00:59

1 Answers1

1

I think a factory method would serve you well here.

Mark your base test fixture as abstract and define a protected abstract method with the same signature as the service constructors. Then, call that method in your base fixture's setup logic.

You will have to implement the abstract method for each derived test class, but I think it's a reasonable compromise.

Note that I also removed the interface from the test class. Based on the sample code you provided, it's entirely redundant with the now-abstract base class. Of course, it might be useful for some reason not apparent from your example--if so, you can keep it.

public class BaseServiceUnitTest<TEntity> where TEntity : BaseEntity
{
    private IBaseService<TEntity> _service;

    public BaseServiceUnitTest(Constructor ctor)
    {
        _service = Create(/* your mocked dependencies here */);
    }

    protected abstract IBaseServce<TEntity> Create(Dependency1 d1, Dependency2 d2);

    //...implemented methods from IBaseServiceUnitTest
}

public class CustomEntityServiceUnitTest : BaseServiceUnitTest<CustomEntity>
{
    // "arg1, arg2"
    protected override IBaseService<CustomEntity> Create(Dependency1 d1, Dependency2 d2) =>
        new BaseService<CustomerEntity>(d1, d2);
}

Addendum

If you really want to make things automagic, you could try the answer to this question. It'll work, but you'll sacrifice "F12-ability" (the ability to find and navigate through references using Visual Studio's shortcuts) and compile-time verification of your constructors and their arguments. Personally, I'd probably use the factory method.


Addendum #2

For completeness, it's also worth noting that if your service-under-test had no constructor arguments, you might also be able to use the new constraint. Based on your comments, it doesn't sound like it will work in this case, but it might be useful another time.

xander
  • 1,689
  • 10
  • 18