Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If we don't verify that private methods are called with unit tests, how do we verify that they are called?

I hate to be bringing this up again, but I'm really trying to understand how to safeguard something with my tests.

I have a public method (below) that calls a private method before calling another method that actually takes some action. I want to make sure that the call to the private method doesn't get removed because that could be catastrophic. I've done some research, here, here, and here, and they all say not to test private methods. I can understand that, I guess, but then how do I safeguard against the removal of this line of code?

As you can see, the public method returns void, so I can't test the results of the public method call. And I have unit tests that test ApplicationShouldBeInstalled() directly.

public void InstallApplications()
{
    foreach (App app in this._apps)
    {
        // This is the line of code that can't be removed. How can I make
        // sure it doesn't get removed?
        if (!ApplicationShouldBeInstalled(app)) { continue; }

        // This simply can't run unless it passes the above call.
        CommonUtility.Container.Resolve<IAppInstaller>().InstallApplication(this, app);
    }                       
}

EDIT - I went with this, based on JerKimball's answer.

Basically, I just use a Mock object (from Moq), and then verify that its method was called the expected number of times.

[TestMethod()]
public void ApplicationShouldBeInstalledTest_UseCase13()
{
    var mockAppInstaller = new Mock<IAppInstaller>();
    mockAppInstaller.Setup(m => m.InstallApplication(It.IsAny<ApplicationServer>(),
        It.IsAny<Application>()));
    CommonUtility.Container.RegisterInstance<IAppInstaller>(mockAppInstaller.Object);

    // Actual test code here

    appServer.InstallApplications();
    mockAppInstaller.Verify(x => x.InstallApplication(It.IsAny<ApplicationServer>(),
        It.IsAny<Application>()), Times.Never());
}

I can't let this go; that edit is just ugly. Even though I have to create an actual mock class, this approach is much cleaner:

Mock implementation:

public class MockAppInstaller : IAppInstaller
{
    public bool Invoked { get; set; }

    public void InstallApplication(ApplicationServer server, Application app)
    {
        this.Invoked = true;
    }
}

Test method:

[TestMethod()]
public void ApplicationShouldBeInstalledTest_UseCase14()
{
    MockAppInstaller mockAppInstaller = new MockAppInstaller();
    CommonUtility.Container.RegisterInstance<IAppInstaller>(mockAppInstaller);

    // Actual test code here

    appServer.InstallApplications();
    Assert.AreEqual(true, mockAppInstaller.Invoked);
}
like image 205
Bob Horn Avatar asked Feb 14 '13 21:02

Bob Horn


2 Answers

I want to make sure that the call to the private method doesn't get removed because that could be catastrophic.

That sounds like it should be easy. Catastrophes are pretty easy to spot. So run a test which calls the public method, and check whether anything catastrophic happened. The code within the private method which guarded against the catastrophe is effectively a visible side-effect of running your public method... whereas the fact that it was due to a private method call is an implementation detail.

So in this case, you should basically create an application which shouldn't be installed (for whatever reason), and validate that when you call InstallApplications it isn't installed.

like image 158
Jon Skeet Avatar answered Oct 29 '22 02:10

Jon Skeet


Here's one possible option...granted, this is very simplistic, but it might be applicable to your situation with a bit of alteration;

Let's say that App looks like:

public class App 
{
    public virtual bool CanBeInstalled {get; set;}
}

And ApplicationShouldBeInstalled looks like:

private bool ApplicationShouldBeInstalled(App app) { return app.CanBeInstalled; }

You could write a unit test that "confirms via expected actions", like so:

void Main()
{
    var sut = new ThingToTest();    

    var mockApp = new Mock<App>();
    var wasCanBeInstalledChecked = false;
    mockApp
       .SetupGet(app => app.CanBeInstalled)
       .Callback(() => wasCanBeInstalledChecked = true);

    // of course, whatever you'd do here to get an app into this class
    sut.AddApp(mockApp.Object);

    sut.InstallApplications();
    Debug.Assert(wasCanBeInstalledChecked == true);
}
like image 21
JerKimball Avatar answered Oct 29 '22 03:10

JerKimball