简体   繁体   中英

c# - unit testing, mock and ninject factory extension

I have a console app where the user inputs a number, and I generate a feature according to that number. I have used Ninject.Extensions.Factory for that, here are the bindings:

    Bind<IFeature>().To<FirstFeature>().Named("1");
    Bind<IFeature>().To<SecondFeature>().Named("2");
    Bind<IFeature>().To<ThirdFeature>().Named("3");
    Bind<IFeatureFactory>().ToFactory(() => new UseFirstArgumentAsNameInstanceProvider());

The code I want to test is:

constructor:

public FeatureService(IFeatureFactory featureFactory, IUILayer uiHandler, int howManyFeatures)
    {
        this.featureFactory = featureFactory;
        this.uiHandler = uiHandler;
        this.howManyFeatures = howManyFeatures;
    }

method under test:

public async Task startService()
    {
        bool isBadInput = false;
        string userSelection = null;
        uiHandler.displayMenu(howManyFeatures);
        userSelection = uiHandler.getSelection();
        while (!userSelection.Equals((howManyFeatures+1).ToString()))
        {
            IFeature feature = null;
            try
            {
                feature = featureFactory.createFeature(userSelection);
                isBadInput = false;
            }
            catch (ActivationException ex)
            {
                uiHandler.displayErrorMessage();
                isBadInput = true;
            }
            if (!isBadInput)
            {
                await feature.execFeature();
            }
            uiHandler.displayMenu(howManyFeatures);
            userSelection = uiHandler.getSelection();
        }
    }

As you can see, when I try to createFeature , I catch the ActivationException , meaning that the user has input invalid selection (ninject fails to get the concrete class), and execFeature is not called.

I am trying to write a unit test to test that when a user inputs a valid selection, the method execFeature is called .

Here is the test:

    [TestMethod]
    public void WhenUserEnterValidSelectionExecFeatureCalled()
    {
        //Arrange
        Mock<IFeature> featureMock = new Mock<IFeature>();
        Mock<IConsoleService> consoleServiceMock = new Mock<IConsoleService>();
        // mock user input 7
        consoleServiceMock.Setup(c => c.ReadLine()).Returns("7");
        IUILayer uiLayer = new ConsoleUILayer(consoleServiceMock.Object);
        Mock<IFeatureFactory> featureFactory = new Mock<IFeatureFactory>();
        featureMock.Setup(t => t.execFeature());
        featureFactory.Setup(t => t.createFeature(It.IsAny<string>())).Returns(featureMock.Object);
        // init FeatureService with 3 features
        IFeatureService featureService = new FeatureService(featureFactory.Object, uiLayer, 3);

        //Act
        featureService.startService();

        //Assert
        featureMock.Verify(t => t.execFeature());
    }

As you can see - I an creating a consoleMock with user input of "7" , and when I create the FeatureService I put 3 in the howManyFeatures - Test should fail (no concrete implementation).

Now, when I run my program normally - if I input "7", the program acts as expected and outputs an error message.

When I run the test, every input to the consoleMock besides the HowManyFeatures + 1 passes the test ( HowManyFeatures +1 fails because it doesn't go into the while ), and it shouldn't be like that - it should fail for the number that don't have a concrete IFeature implementation (only 1, 2 and 3 have concrete implementations).

How do I solve this? Should I "bring" the Ninject Bindings into the Tests project? should I even test this method? or its all useless?

Any thoughts are appreciated

You don't need to bring ninject binding to your test, your FeatureService does not need to know that your IFeatureFactory is based on ninject binding and it does not care about it.
What you need to do is setup your IFeatureFacotory mock properly, now your mock returns the same IFeature no matter what is the input because this is the behavior you told it to use be doing this:

 featureFactory.Setup(t => t.createFeature(It.IsAny<string>())).Returns(featureMock.Object);

If you want that it will throw ActivationException when gets number larger than 3 just setup this desired behavior instead:

featureFactory.Setup(t => t.createFeature(It.Is<string>(input => (int)input>3 ))).Throws(new ActivationException()); 

By the way you should probably mock your IUiLayer straight and inject it to the FeatureService instead of mocking your consoleService and use it in your real UiLayer implementation, it will make your testing much easier.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM