If you’re having trouble testing legacy code, mock a logger

I was working on some legacy code (“legacy” meaning it has no automated test coverage) and I really wanted to write some automated acceptance tests for it.  The problem is, I could not think of a way to do that without refactoring the code first.  I did not want to do this because “it was already working” and the refactoring could have introduced new bugs.  I had a chicken and egg problem: I didn’t want to modify code until I had automated tests but I had to modify code to write the automated tests.  Which comes first?

I ended up using a trick that minimally changes the production code, actually improves the production code base, and allows me to write automated tests.  It’s these two simple steps:

  1. Add and use a logger in the production code you want to test.
  2. Write an automated test that mocks the logger.

In the rest of the article I’ll give an example that was close to my situation.  Here’s what the code looked like:

I specifically wanted to check if  RemoteApi.getInstance().save(object); was called in this deepInTheBowelsOfTheProject  method.  Your reaction might be, “why not just mock out the RemoteApi  if that’s what you want to check?”  Well I agree, that would be the most straightforward way to do it but that wouldn’t work in this situation: The entry point of my test ends up calling RemoteApi.getInstance().save(object);  in many places.  I only care if it was called in this method.

I struggled with this for a while and came up with a solution I summarized above: I added a logger to this class and for the purposes of my test, I mocked it out.  The class changed to look like this:

With that logging statement in place, I could mock out the logger and run an acceptance test to check if that line of code ran.  Also, there’s very little risk to this change and it will have the side effect adding better logs for debugging in the future.  Here’s how my test looks:

This is a very nontraditional approach to testing, but if the alternative adds a lot of risk and takes a lot of time, it’s a very pragmatic thing to do.

Join the email list for bonus content.

8 thoughts on “If you’re having trouble testing legacy code, mock a logger

    • Daniel Kaplan says:

      Hi Jon,

      Thanks a lot for your comment, I really appreciate it. You’ve shown me that in this article I have articulated my thoughts poorly and I need to rewrite some of it. This article isn’t about

      If you’re having trouble debugging legacy code, then use a logger

      Like you said, that has been around for decades and there isn’t really anything unique about saying that. But that’s not was I’m trying to convey. I’m trying to say this:

      If you’re having trouble writing automated test for legacy code, then mock out a logger

    • Daniel Kaplan says:

      Hi Kasper,

      I skimmed the documentation but I was having a little trouble understanding it. A lot of the time I could not tell if it was pointing out a good way or a bad way to do things. Maybe that’s because this documentation seems to start on chapter 3 instead of chapter 1. Where’s this documentation start?

  1. Hi, interesting post, thank you!
    I wonder whether it would be more convenient to use something different than a logger – a kind of a hand-created mock. Obviously it would take more time, but would result in cleaner tests (so you wouldn’t have to assert against log messages, but simply verify whether the mock is in given state – e.g. assertThat(someMock.someMethodWasDone())
    I’m not saying this would be better, only thinking aloud. :)

    Cheers!

    • Daniel Kaplan says:

      Hi Tomek,

      I think you’re absolutely right that a traditional mock would be better. The problem is that for legacy code, it’s rare to find a safe way to put that mock into the production code.

      Mocking a logger for a test doesn’t make a “good” test. But, it provides a safety net. It can help you see that you didn’t break anything when you refactor. Once you refactor, you can use the mock that you suggested and delete the logger test. But if you skip straight to the good mock, it’s too risky: You’ll have to change too much code that you don’t understand yet. You don’t understand yet because it’s a legacy system without tests.

Leave a Reply to Jon Cancel reply

Your email address will not be published. Required fields are marked *