microsoft / UnitTestBoilerplateGenerator

An extension for Visual Studio that generates a unit test boilerplate from a given class, setting up mocks for all dependencies. Supports NUnit, Visual Studio Test, Moq and SimpleStubs.
MIT License
158 stars 51 forks source link

mock object creation fails for objects with generic type parameters #9

Closed BlacKCaT27 closed 7 years ago

BlacKCaT27 commented 7 years ago

Installed product versions

Description

When using constructor injection with objects whose types have generic type parameters, the BoilerPlate Generator fails to create the mock object. The private member variable is not created, the object is missing from the TestInitialize() method, and the Create method uses the text "TODO" in place of the missing variable.

Steps to recreate

  1. Create a class whose constructor takes an argument with a generic type parameter(e.g. ILogger logger)
  2. Select NSubstitute for mocking framework (other frameworks not tested, so they may be affected as well)
  3. Use the extension to generate boilerplate
  4. observe the missing mock object in the member variable area, the TestInitiaze() method, and the Create method.

Current behavior

See description

Expected behavior

Injected objects utilizing generic type parameters are generated and mocked just like non generic objects.

RandomEngy commented 7 years ago

Thanks for the report! Fixed in 1.5.2.

BlacKCaT27 commented 7 years ago

Thanks for the update!

Things are working better but unfortunately I'm still seeing an issue with this.

Previously, the field wasn't being created at all, which was obviously failing compilation. Now, the field is being created, but the corresponding generic type parameter isn't being generated so compilation still fails.

I feel like this may be partially my fault, as my post didn't provide a good example code of what I'm talking about. Allow me to remedy that here. For clarity, I'll use ASP.Net's ILogger generic interface as an example:

If I have a class with this definition:

public class MyClass : IMyClass{
    private ILogger<IMyClass> _logger;

    public MyClass(ILogger<IMyClass> logger){
        _logger= logger;
    }
}

This is what I would expect to be generated by the generator:

namespace MyProject.MyNameSpace
{
    [TestFixture]
    public class MyClassTests
    {
        private ILogger<IMyClass> _mockLogger;

        [SetUp]
        public void TestInitialize()
        {
            _mockLogger = Substitute.For<ILogger<IMyClass>>();
        }

        [Test]
        public void TestMethod1()
        {
            MyClass myClassUnderTest = this.CreateMyClass();
        }

        private MyClass CreateMyClass()
        {
            return new MyClass(
                _mockLogger);
        }
    }
}

However, this is what v1.5.2 of the generator creates:

namespace MyProject.MyNameSpace
{
    [TestFixture]
    public class MyClassTests
    {
        private ILogger _mockLogger;

        [SetUp]
        public void TestInitialize()
        {
            _mockLogger = Substitute.For<ILogger>();
        }

        [Test]
        public void TestMethod1()
        {
            MyClass myClassUnderTest = this.CreateMyClass();
        }

        private MyClass CreateMyClass()
        {
            return new MyClass(
                _mockLogger);
        }
    }
}

Notice the type of _mockLogger and the type parameter being given to Substitute.For<>(): The type parameter of ILogger is not being carried forward into the generation, but since MyClass needs that more specific type (you can't give just a generic ILogger to something that wants ILogger when T is defined), compilation fails.

Does this make sense? I can open a new issue if you'd prefer.

RandomEngy commented 7 years ago

You'll need to update your custom template to take advantage of the new feature. Change $InterfaceNameBase$ to $InterfaceMockName$ and change $InterfaceName$ to $InterfaceType$ .