Static initialisers are a major hurdle for testing. If you have a static initialiser that for instance performs a request to the database then you cannot access that class in a unit test. Let alone mock it. In fact only importing this class will execute the query. So even if you're trying to test some other class, you cannot, since the offending class is contaminating every import of it with non testability. You want to make sure to remove the static initialiser so that other classes can be tested in isolation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static Map<String, Object> translationMap; | |
static { | |
translationMap = HibernateUtil.executeQuery("Select ..."); | |
} |
The solution is to replace the static initialiser with a lazy initialiser. This will allow you to load the class for mocking. You'll probably have to also replace a static method by an instance method in order to avoid calling the real method and so properly test classes that are calling the offending class, but first things first.
Assuming that your static initialiser is initialising a static field of your class:
- Encapsulate the access to the field with a simple getter
- Use the *Extract Method* refactoring to extract all the code from the static initialiser
- In the getter initialise the field *if it is null* by calling the extracted method
- Delete the static initialiser
Your next step will likely be to replace the public methods using this getter by instance methods so that those can be tested (example).
Let's have a look at an example of the code before refactoring
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class UnTestableDependency { | |
private static Map<String, Object> translationMap; | |
static { | |
translationMap = HibernateUtil.executeQuery("Select ..."); | |
} | |
// this static method is called by the class you'd like to test | |
public static String getTranslation(String key, Locale locale) { | |
Object keys = translationMap.get(locale.toString()); | |
// logic ... | |
// etc ... | |
return null; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class LazyInitialiserDependency { | |
private static Map<String, Object> translationMap; | |
// no more static initaliser, the class is mockable, though we'll have to go further by | |
// adding a version of getTranslation as an instance method if we want to mock it | |
// this static method is called by the class you'd like to test | |
public static String getTranslation(String key, Locale locale) { | |
Object keys = getTranslationMap().get(locale.toString()); | |
// logic ... | |
// etc ... | |
return null; | |
} | |
private static void initialiseTranslationMap() { | |
translationMap = HibernateUtil.executeQuery("Select ..."); | |
} | |
private static Map<String, Object> getTranslationMap() { | |
if (translationMap == null) { | |
initialiseTranslationMap(); | |
} | |
return translationMap; | |
} | |
} |
Having "Replaced static initialiser with lazy initialiser" we're almost there. We can safely import this class, but we can't yet mock the static method. In order to make it mockable we can simply add an instance method as a proxy to the static method, then use the instance method instead of the static method .