Mocking Static Code Without a Mocking Library

Our mocking library was deprecated. With 13,000 files of Java code on the brink of becoming untestable, we needed a solution that didn't require major surgery.

The Problem

Example

Here is some code which finds how many apples we have in the database:

class FruitSorter {
 long countApples() {
   List<Record> totalFruit = MyDatabase.query();
   return totalFruit.stream()
                    .map(Record::getFruit)
                    .filter("Apple"::equals)
                    .count();
 }
}

I want to unit test this stream() logic without having to stand up my database and insert data to it (since that’s outside of my “unit”). Let's look at MyDatabase:

class MyDatabase {
 static List<Record> query() {
   ... // actually hit the database
 }
}

Not very mockable. Since this method is static, there's no way to instantiate anything or pass anything in (“dependency injection”) to redefine its behavior for a test. Some mocking libraries support this, but each one has its own quirks which didn’t align with our goals. We were facing a full-scale overhaul just to get off of this testing library.

Static Code: Methods or variables that belong to the class itself, rather than to any individual instance of it. You call them directly on the class (e.g., MyDatabase.query()) without creating an instance of it first. They are effectively global — which makes them convenient, but notoriously hard to swap out in tests, as the class definition cannot be changed at runtime.

The Pattern

I started seriously looking for ways that we could escape these libraries. We started using stubs (a.k.a., anonymous classes, typically, which override a method’s original behavior) for almost all of our use cases, injecting dependencies like crazy. But we still got hung up on our static utilities. A few of our utilities are wrapped in singletons, and they gave me an idea. I could replace the singleton with a stub implementation—literally go into the SINGLETON/INSTANCE field and set it to a different object that has all the test behavior I need.

Stub (a.k.a. Mock): A minimal, fake implementation of a class or method that returns predefined data instead of doing real work. For example, instead of actually hitting the database, the MyDatabase stub just hands back a hardcoded list of fruits, enabling the test to verify the sorting logic without needing a live database.
Singleton: A design pattern where a class is set up so only one instance of it can ever exist at a time. Think of it like a global resource — there’s exactly one MyDatabase, and everyone else in the program’s execution space shares it. This is similar in utility to Static Code but enables the below exploit for stubbing during testing.

Example

We start by converting the utility to a singleton:

class MyDatabase {
 static MyDatabase SINGLETON = new MyDatabase();
 // NOTE: query() is no longer static
 List<Record> query() {
   ... // actually hit the database
 }
}

And suddenly we can inject our own behavior!

class FruitSorterTests {
 @Test
 void test() {
   final MyDatabase original = MyDatabase.SINGLETON;
   try {
     MyDatabase.SINGLETON = new MyDatabase() {
       @Override
       List<Record> query() {
         return List.of(new Record("Apple"),
                        new Record("Orange"),
                        new Record("Banana"),
                        new Record("Apple"));
       }
     };

     // Code under test
     Assertions.assertEquals(2, new FruitSorter().countApples());

   } finally {
     MyDatabase.SINGLETON = original;
   }
 }
}

Tada! We've removed our database dependency for testing without having to change the MyDatabase contract too aggressively.

Cleaning It Up

Now of course, all the try/finally stuff can get cumbersome, so we want to make wrappers for these:

class MyDatabaseStub extends MyDatabase implements AutoCloseable {
 private final MyDatabase original;

 MyDatabaseStub() {
   original = MyDatabase.SINGLETON;
   MyDatabase.SINGLETON = this;
 }

 @Override
 void close() {
   MyDatabase.SINGLETON = original;
 }
}

Which is reusable and tidies up the test a bit:

class FruitSorterTests {
 @Test
 void test() {
   try(final MyDatabaseStub stub = new MyDatabaseStub() {
       @Override
       List<Record> queryImpl() {
         return List.of(new Record("Apple"),
                        new Record("Orange"),
                        new Record("Banana"),
                        new Record("Apple"));
       }
     }) {
     // Code under test
     Assertions.assertEquals(2, new FruitSorter().countApples());
   }
 }
}

Harden It

We don’t want to expose the actual SINGLETON field everywhere, so we try to keep them package-private and deliver the stub as a test fixture in the same package so that it’s the only place which accesses the SINGLETON field. We also wrap the swappable singletons in an object which protects against misuse and can provide some extra benefits (lazy loading, for example). Implementing this wrapper is left as an exercise for the reader. But that’s it! Our “Singleton Swapping” pattern.

Tradeoffs

This pattern isn't without baggage. You're mutating global state at runtime, which means parallel tests that use the same singleton will race against each other. The AutoCloseable approach helps with cleanup, but if someone forgets it, you get cascading test pollution. And fundamentally, it's a workaround -- statics are hard to test because they represent tight coupling, and this lets you avoid addressing that rather than fixing it.

Despite all of this, I'm surprised to have never seen this pattern before. I did some digging and it looks like Michael Feathers describes the general idea in Working Effectively with Legacy Code as finding "seams" — places where you can change behavior without changing code. This was a seam I found that happened to apply generally. It could also be viewed as a targeted take on the Service Locator pattern.

We decided the tradeoffs were worth it. Another fantastic benefit of this over a mocking library is that signature matching is enforced at compile-time, which we always wished we had before. If you're starting fresh, maybe prefer Dependency Injection. In a large, existing codebase built on statics or services, this pattern gets you testability without a rewrite.

Brendan Boyd
Senior Software Engineer
Brendan Boyd is a Senior Software Engineer at Nextworld, where he’s spent over six years architecting the things that make the platform run, from logic blocks to scalable infrastructure. Off the clock, he climbs mountains, makes music, and chases a 1-year-old he’ll tell you is his best work yet.