0

First, let me introduce you my project.

We are developping an app in which user can work with programs. As programs I mean list of instructions for a confidential use.

There are different types of Programs all inheriting from the Program abstract base class.

As the user can create different types of program, we developped a ProgramManager that can instantiante any type of Program by its type. We don't need to instantiate the abstract class but all the concrete classes (and it works) but as concrete Program have same methods (AddNewChannel, Save, ...) we handle them like Programs.

Here's a sample of code:

public Program CreateProgram(Type type)
    {
        Program program = Activator.CreateInstance(type) as Program;
        program.Directory = ProgramsPath;

        int nbChannels = 2; //not 2 but a long line searching the correct number where it is.

        for (int i = 1; i <= nbChannels; i++)
        {
            program.AddNewChannel(i);
        }

        program.Save();
        return program;
    }

What I now have to do is test this function and I don't want to duplicate the unitTests I already made for the different Program classes.

As an example, here is one of my test functions (for the Save method) with it's init. I store the types I need to test in a xml file.

    [TestInitialize]
    public void TestInitialize()
    {
        if (!TestContext.TestName.StartsWith("GetKnownTypes"))
            type = UnitTestsInitialization.applicationHMIAssembly.GetType((string)TestContext.DataRow["Data"]);
    }


    [TestMethod]
    [DataSource("Microsoft.VisualStudio.TestTools.DataSource.XML",
               "|DataDirectory|\\" + DATA_FILE, "Row",
                DataAccessMethod.Sequential)]
    public void SavedProgramCreatesFile()
    {
        Program program = Activator.CreateInstance(type) as Program;
        program.Name = "SavedProgramCreatesFile";
        program.Directory = DIRECTORY;

        program.Save();

        string savedProgramFileName = program.GetFilePath();

        bool result = File.Exists(savedProgramFileName);

        Assert.IsTrue(result);
    }

All my concrete Program classes have been tested separatly.

Thereby, I would like to test if the following methods program.AddNewChannel and program.Save are called.

I gave a look at Moq but the first problem is that the method Save is not abstract.

Also, using Activator doesn't allow me to make a Mock<Program>.

I tried the following in a unit test in order to try to instantiate the mock and use it like a program:

    [TestMethod]
    [DataSource("Microsoft.VisualStudio.TestTools.DataSource.XML",
               "|DataDirectory|\\" + DATA_FILE, "Row",
                DataAccessMethod.Sequential)]
    public void CreateProgram_CallsProgramSaveMethod()
    {
        Mock<Program> mock = new Mock<Program>();
        mock.Setup(p => p.AddNewChannel(It.IsAny<int>()));

        Program program = pm.CreateProgram(mock.Object.GetType());
        mock.Verify(p => p.Save());
        mock.Verify(p => p.GetFilePath(It.IsAny<string>()));
        mock.Verify(p => p.AddNewChannel(It.IsAny<int>()), Times.Exactly(ProgramManager.NB_MACHINE_CHANNELS));
        Assert.IsNotNull(program);
        program.DeleteFile();
    }

Which was inspired by this question: How to mock An Abstract Base Class

And it works until it reaches the line program.AddNewChannel(i); in the for loop. The error is the following:

System.NotImplementedException: 'This is a DynamicProxy2 error: The interceptor attempted to 'Proceed' for method 'Void AddNewChannel(Int32)' which is abstract. When calling an abstract method there is no implementation to 'proceed' to and it is the responsibility of the interceptor to mimic the implementation (set return value, out arguments etc)'

It seems that the setup doesn't work but I might understand why. (I try to instantiate a subtype of Proxy which doesn't implement verify method)

I also tried to use a Proxy over my program class which would implement an interface which would contain the methods I needed but the problem here is the activator again.

Can anyone suggest me any way of testing those method calls ? (Even if I need to change my method CreateProgram)

I gave a look here: How to mock non virtual methods? but I am not sure this would be applicable to my problem.

I use MSTests for my unittests.

NOTICE

Everything else works fine. All my other tests pass without troubles and my code seems to work (Tested by hand).

Thanks in advance.

Maël Pedretti
  • 718
  • 7
  • 22

2 Answers2

2

The root cause of the problem is that you're using a type as a parameter, which you then use to create an instance of this type. However, you're passing in the type of an abstract class, which is specifically not made for instantiating. You need to work with the concrete classes directly.


Thereby, I would like to test if the following methods program.AddNewChannel and program.Save are called.

That's not sufficient as a test. You want to test whether these methods work as expected, not just if they're called and then assume that they work.

What you're describing is a (very rudimentary) integration test, not a unit test.


I don't want to duplicate the unitTests I already made for the different Program classes

This is a very dangerous decision. Part of the idea behind unit testing is that you create separate tests for different (concrete) objects. The tests need to be as segregated as is reasonably possible. You're trying to reuse testing logic, which is a good thing, but it needs to be done in a way that it does not compromise your test segregation.

But there are ways to do it without compromising your test segregation. I only have testing experience with NUnit but I assume a similar approach works in other frameworks as well.

Assume the following:

public abstract class Program 
{
    public bool BaseMethod() {}
}

public class Foo : Program 
{
    public bool CustomFooMethod() {}
}

public class Bar : Program 
{
    public bool CustomBarMethod() {}
}

Create an abstract class testing method:

[TestFixture]
[Ignore]
public class ProgramTests
{
     public virtual Program GetConcrete()
     {
         throw new NotImplementedException();
     }

    [Test]
    public void BaseMethodTestReturnsFalse()
    {
        var result = GetConcrete().BaseMethod();

        Assert.IsFalse(result);
    }
}

[Ignore] ensures that the ProgramTests class does not get tested by itself.

Then you inherit from this class, where the concrete classes will be tested:

[TestFixture]
public class FooTests
{
    private readonly Foo Foo;

    public FooTests()  
    {
        this.Foo = new Foo();
    }

    public overrides Program GetConcrete()
    {
        return this.Foo;
    }

    [Test]
    public void CustomFooMethodTestReturnsFalse()
    {
        var result = this.Foo.CustomFooMethod();

        Assert.IsFalse(result);
    }
}

BarTests is similarly implemented.

NUnit (presumably other testing frameworks as well) will discover all inherited tests and will run those tests for the derived class. Every class that derives from ProgramTests will therefore always include the BaseMethodTestReturnsTrue test.

This way, your base class' tests are reusable, but each concrete class will still be tested separately. This maintains your test separation, while also preventing you having to copy/paste test logic for every concrete class.


I also noticed this:

Mock<Program> mock = new Mock<Program>();
mock.Setup(p => p.AddNewChannel(It.IsAny<int>()));
Program program = pm.CreateProgram(mock.Object.GetType());

I don't understand the purpose of this code. How is it any different from simply doing:

Program program = pm.CreateProgram(typeof(Program).GetType());

As far as I can see, both the mock and its setup are irrelevant since you're only looking at its type and then having CreateProgram() create a new object for you anyway.

Secondly, and this refers back to my example of testing the concrete classes, you shouldn't be testing with Program directly, you should be testing your derived program classes (Foo and Bar).

This is the root cause of the problem. You're using a type as a parameter, which you then use to create an instance of this type. However, you're passing in the type of an abstract class, which is specifically not made for instantiating. You need to work with the concrete classes directly.

Flater
  • 12,908
  • 4
  • 39
  • 62
  • I think another root course of the trouble is that they are mocking an abstract class where they should mock interfaces. Most mocking frameworks have limitations in mocking classes, and some frameworks only mock interfaces by default. – ChristianMurschall Sep 18 '18 at 14:07
  • 1
    @ChristianMurschall: Mocking interfaces is a good idea but it is not a _requirement_. When you look at it, abstract classes and interfaces are pretty much the same. The only difference is that you can implement multiple interfaces (but only inherit from one class), and that abstract classes can already contain method bodies and protected properties. I agree it's good practice to implement interface segregation, but there's no technical consideration for the currrent problem, interfaces would purely be a matter of style and good practice. – Flater Sep 18 '18 at 14:10
  • 1
    @ChristianMurschall: That is to say, I haven't ever run into a testing/mocking issue where I was only able to use an interface and not a class. – Flater Sep 18 '18 at 14:11
  • Well, I have to say that I rarely mock testobjects. Mostly to fake a database connection. But I did fumble a bit around with this problem here. Their abstract base class `Program` contains two methods: an abstract and an non abstract. I was not able to get the requirement to work to verify, that both methods are called by `CreateProgram`. When I used an interface it was easy. – ChristianMurschall Sep 18 '18 at 14:17
  • I've added precisions in my question. – Maël Pedretti Sep 19 '18 at 05:35
1

Create a wrapper interface and class around Activator, then pass the type to that:

public interface IActivatorWrapper
{
    object CreateInstance(Type type);
}

public class ActivatorWrapper : IActivatorWrapper
{
    public object CreateInstance(Type type)
    {
        return Activator.CreateInstance(type);
    }
}

Use this instead of Activator directly, then mock the IActivatorWrapper to return whatever mock object you want.


Another idea to help with your problem would be to add an IProgram interface to your abstract Program class, then use that to refer to your concrete Program instances. This might also help you, should you ever want to write a concrete Program with a different base class.

g t
  • 7,287
  • 7
  • 50
  • 85
  • 1
    By itself, this doesn't quite fix the issue. Nothing is preventing someone from doing `CreateInstance(typeof(MyAbstractClass))`, which will still lead to the same problem; you can't instantiate abstract classes. While yes, OP can mock the activation and thus circumvent it, it doesn't address the core issue of OP explicitly wanting the code to instantiate his abstract class directly. – Flater Sep 18 '18 at 15:54
  • @Flater I don't want the code to instantiate my abstract type and the user won't be able to do it neither because he only has a list of concrete class types to instantiate. Please consider reading my question again, I've added some precisions – Maël Pedretti Sep 19 '18 at 05:37
  • I created a wrapper and everything works fine. Thanks. – Maël Pedretti Sep 19 '18 at 07:46