Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing code which calls static methods

I've read most SO related questions ( here, here and there). The last question proposes four alternatives to make code which calls static methods unit-testable. I want to ask about my particular case: We have a "business logic layer" or "rules" project which contains 45 static classes (no state, just static methods). Moreover, they are not easily testable by themselves: most of them access the database and file system. It's not that bad, anyway: to access the database, they use the unique instance of some Mapper class (all Mappers are Singletons). Whenever I try to unit test something, I run into this wall. The biggest problem is that this is very, very important code, and changes to it should be planned very carefully. My question: How should I go about making this more unit testable? Should I write 45 interfaces and use dependency injection? Even so, how do I stub/mock Mappers?

PS: I've been reading Michael Feathers' "Working with Legacy Code", so direct references are welcome (other books too :)

Edit: Since some people said solutions might be platform-dependent, I'm working on .NET (C# and some VB.NET)

like image 276
dario_ramos Avatar asked Nov 08 '11 12:11

dario_ramos


People also ask

How do you call a static method in unit testing?

All you need to do is wrap the static method call inside an instance method and then use dependency injection to inject an instance of the wrapper class to the class under test.

When static methods are called?

Static methods are the methods in Java that can be called without creating an object of class. They are referenced by the class name itself or reference to the Object of that class.

Can we use this to call static methods?

The "this" keyword is used as a reference to an instance. Since the static methods doesn't have (belong to) any instance you cannot use the "this" reference within a static method.


2 Answers

The current situation is probably that no one dares to change anything in the code because it might break in unexpected ways. Make sure that everyone understands that you are improving the situation: Your changes might break the code but unlike before, those breakages will be found and once they have been found, they will be fixed forever.

That said, the next step depends on your experience and your credit with the team. If you want to play safe, use code like this (Java syntax):

Mapper {
    public static Mapper INSTANCE = new Mapper(); // NEW code

    protected void doImpl() { // NEW CODE
        ... code copied from impl()... // OLD code, NEW PLACE
    }

    public static void impl() { // OLD code
        INSTANCE.doImpl(); // NEW code
    }

    // OLD code ...
}

This is a very simple change which allows you to overwrite INSTANCE from your tests. For production code, you don't do anything and the default will make the code behave exactly like before.

That way, you can replace one method at a time. You can stop following this path at any time - each change takes only a couple of minutes and it's a refactoring: The code does exactly what it did before. Since each change is so small and can't break anything, you can replace one method, write all the unit tests that you couldn't write before, rinse, repeat. Lastly, if you don't want/need to rework all static methods, this approach gives you all the leeway you could ask for.

In a second step, you can introduce DI or any other technology which will make you happy. The advantage of this approach: When you come to the complex changes, you will already have unit tests that'll protect you.

If you started with DI, you'd have to change a lot of code in all kinds of places - without proper unit tests that could protect you.

like image 98
Aaron Digulla Avatar answered Oct 21 '22 09:10

Aaron Digulla


Make the interface of the Mapper class and replace the mapper functionality with a new moc implementation. I think that you will need to implement the abstract factory which will generate the instances of Mappers. So make an IMapperFactory and two implementations of this DBMapperFactory and MocMapperFactory pass the instance of this object to your code where you access the Mappers and generate these with using this instance.

Gl.

like image 3
AlexTheo Avatar answered Oct 21 '22 08:10

AlexTheo