A big part of my job is maintaining old code bases. Unfortunately, it’s rare that these code bases were written with testability in mind. As a result, I start with very little confidence in the changes I’m making: I’m not sure how the code works so I can’t safely say that my change are safe. In my opinion, the best way to gain confidence in a code base is to write automated tests for it. But usually easier said than done.
Here’s a problem I always have to deal with: I can write a bunch of automated tests for the legacy system but the tests will eventually access a database or call a remote API that is super slow. Before I know it, my tests take 10+ minutes to run. Once that happens, I run them less frequently because the wait is a pain, and when that happens, it takes me longer to notice if I’ve introduced a bug.
Another issues is the functionality probably isn’t idempotent. For example, the first time I run the test it will insert a row into a database. The second time I run the test, the database will have two rows. This may change the result of my expected outcome each time.
This situation is often such a pain that I just wish I could replace the database/API with some code of my choosing. But achieving this is like solving a very difficult puzzle. If class A creates class B which creates class C , and so on, how do you write a test that calls class A that uses a mocked version of class Z ?
This is where monkey patching would be really handy. In other languages, the classes are mutable so you can easily rewrite their logic to make testing easier. But when you run the actual code in production, these test mutations do not effect anything. To put it another way: Monkey patching gives you the ability to rewrite any method or class whenever you want. This flexibility makes it easy to test code that collaborates with code you don’t want to invoke.
Java doesn’t have this feature and that’s why dependency injection frameworks were created. Without DI, you have to use the new keyword everywhere. Using the new keyword is like chiseling your code in stone to say, “This is exactly what this implementation should be here under all circumstances”. It makes it very difficult to change the functionality of that class for testing purposes (well, for any purpose, really). Dependency injection works, but it’s not a switch you can flip on: It takes a lot of effort, change and risk to get a legacy system to use a DI framework.
I wish Java would just let me say, “in this test, replace all code that says new Foo() with new FooMock() “. And I’m not the only one who wishes that. There is a stack overflow discussion about it here. Some solutions in there include Aspect Oriented Programming, Byte Code Manipulation, creating your own ClassLoader, and mucking with the classpath. None of these are easy enough to use, in my opinion.
Here’s what I’m looking for:
- I should be able to monkey patch a method/class in one test but that should not effect the next test that is run.
- I should not have to modify any of the production code. That would be too risky because I might accidentally introduce new bugs.
- This tool should be easy to understand by someone who hasn’t heard of it. Ideally, it would have a very nice API like Mockito.
- It should work with JDK 1.5 and higher. That’s the JDK version that a lot of legacy code is still stuck at.
- It should allow me to test the way that I want. For example, it should allow me to monkey patch a deeply nested class while I use Spring to initialize everything else.
Normally when I post an article that complains about something I like to provide some solutions to the problem. But unfortunately, I only know of a tool that might satisfy these requirements. It’s called PowerMock and to be honest, I’ve never used it before. I think I’ll try it out and report back on my findings in another article. One limitation that I already know of is that this tool is meant to be used for testing and a lot of other people might be interested in the ability to monkey patch outside of tests. Personally, that’s not something I normally run into as an issue so I’m fine with this limitation.