Every time you break the build, God kills a kitten. Please think of the kittens.
When it comes to unit testing you might find yourself wanting to test private methods. Here’s four solutions, some much better than others.
1. Don’t test private methods (refactor!)
If you find yourself needing to test private methods, you’re code is trying to tell you something – listen up!
Unit tests should test the behavior of classes and not the implementation details. Unit tests should be able to naturally cover your private methods. If you find this difficult or impossible to do, consider it a code smell.
Testing implementation will become a barrier to refactoring since you will be unable to change the implementation without updating tests. As Charles Miller put it “The amount of work you have to do to improve your code becomes the amount needed to change the private methods, _plus_ that required to change the tests. As such, you’re less likely to make the improvement.”
If you choose to test private methods keep in mind you are likely taking on some technical debt.
Pros: You’re not testing private methods. Best solution. Results in better design.
Cons: Can sometimes be tricky to implement, especially in legacy applications.
2. Use reflection
Thanks to reflection, access levels are more of a suggestion than a requirement. With a little bit of code, we can access any private method (or field for that matter).
First use reflection to change the access level:
MySuperCoolDao mySuperCoolDao = new MySuperCoolDao();
// get method by name "isThisCool" and signature
Method isThisCoolMethod = mySuperCoolDao.getClass().
getDeclaredMethod( "isThisCool", String.class );
// make method accessible
getCoordiantesMethod.setAccessible( true );
Then we use the newly accessible method:
boolean isWindowsCool = isThisCoolMethod.invoke(mySuperCoolDao, "Windows");
boolean isUbuntuCool = isThisCoolMethod.invoke(mySuperCoolDao, "Ubuntu");
assertFalse("Windows is not cool…", isWindowsCool);
assertTrue("Ubuntu is very cool", isUbuntuCool);
A big negative to this method is maintainability. In fact this just happened to me today: I refactored out a private method and as expected the test failed. Unlike other solutions (like the package level solution below), the failure was at runtime – not compile time.
Pros: Relatively easy and quick. Does not break encapsulation.
Cons: Tests become busier. More difficult to keep tests updated with production code. Security settings could prevent this technique from working.
3. Use package level access
This solution is very easy, simply remove private to get the (default) package level access. As long as your tests are in the same package as the production code, no problem. Since it’s good practice for your unit tests to be in the same package (but in a separate source folder), this works well.
Right out of college, this was the first way I learned to handle these situations. Almost everything method was package level. It never really smelled quite right, but the postives out weighed the negatives. In hindsight it might not have been the best solution, but there are far worse things.
Pros: Quick and easy.
Cons: Breaks encapsulation. Classes become a little noisier (although not as bad as using public).
4. Mix production and test code
This is the worst of all the solutions, so I hesitate to even bring it up. By making your test class an inner class in your production code, your tests will have access to private methods. Unfortunately this solution leaves your production code dependent on your test code.
Challenge: is there a legitimate reason to do this? I can’t think of one.
Pros: At least you’re writing tests! Weaksauce, I know.
Cons: Production code contains more than production code.
Quick update: Chad Bradley informed me that it is possible to test private methods using Groovy. I would be hesitant to use the technique without knowing for certain if it’s a bug that will get fixed or an actual language feature.
This has been an absolutely frustrating few hours (I’m trying hard not to think about how long I’ve fiddled with this issue, ugh).
First Stab at Troubleshooting
Ping yahoo.com? Nothing.
Ping 192.168.0.1? Nothing.
Turn off/on AirPort? No good.
Turn off/on the MacBook? No good.
Sanity check: ping 127.0.0.1? Great, I could get home, but I couldn’t get away.
I finally managed a clue to the problem beyond the vauge “it doesn’t work anymore”. While digging into the networks settings and making sure everything at least looked okay, everything did not look okay. In fact, one thing looked very wrong: you’re using a “self assigned IP address, may not be able to connect to internet“.
So I was off to Google for hours of fruitless searching.
Following the Cookie Trail
After manually specifying my DHCP setting, three new clues surfaced in succession.
The second clue: I could not connect to my router’s browser interface (nor could I ping it).
The third clue: KisMAC was running like a champ.
With DHCP settings manually configured and successfully connecting to at least something on the router, what could possibly be wrong?
By this point my wife was no longer asking “what’s wrong?” every time I let out a deep sigh.
Villechaize: Ze clue! Ze clue!
After removing the manual DHCP settings, I tried again. Same problem about a “self assigned IP”. Apparently millionth time was not a charm (supposedly this is the definition of insanity).
Desperately, I decided to take a peak at my router’s logs. I don’t know what prompted this, but it turns out it was my final piece to the puzzle:
Jul 7 20:15:28 daemon.info dnsmasq: DHCPINFORM(br0) nnn.nnn.nnn.nnn xx:xx:xx:xx:xx:xx
Jul 7 20:15:28 daemon.info dnsmasq: DHCPACK(br0) nnn.nnn.nnn.nnn xx:xx:xx:xx:xx:xx
I must be blocking something, somehow.
I took a guess and disabled the software firewall. Changing the firewall from “set access for specific services and applications” to “allow all incoming connections” did the trick. Great success!
I remember turning on the “set access for specific services and applications” a long long long time ago. So why did it take so long to surface? I’m really not sure. Right now I’m just hoping this really was my problem.