Unit Testing in Java

Recently, at work I had to give a session to the incoming college graduates about Unit Testing in Java. I graduated last year. I found testing difficult. "Look, I just built shiny feature X and it works!! Why on earth does it need test cases?". Even harder is to recognize how and what to test. "I'm testing my code X, so I should mock out all it's dependencies right? That's how I'll know if MY code works!"

Motivation Behind Unit Testing

It's difficult to convince a new developer that Unit Tests are necessary. They've been doing just fine without them. And good Unit Tests, like any good code needs motivation. So what's in it for you?


It's true that Unit Tests add little value to your overall application. In a Java web server, the user is more likely to hit a route. If your project has no tests at all, start by adding Integration Tests. They are the best tradeoff between speed and effectiveness. So why do I still write Unit Tests?


David K Piano recently tweeted this. I completely agree. Where I disagree is I find value in making sure the developer is working correctly. Let me give an example:-

// This is a simple function that takes a list and returns the sum of its elements
// Yes, I know this can be done in a more elegant way with streams, but bear with me
public static Integer sumList(List<Integer> integerList){
Integer sum = 0;
for(Integer element : integerList){
sum += element;
}
return sum;
}

And now I start writing Unit Tests for this function

@Test
public void shouldTakeAnEmptyList() {
List<Integer> integerList = new ArrayList();
Integer result = sumList(integerList);
// Wait. This returns 0. But so does [-1, 1].
// That does not seem intuitive...
// Maybe, I should throw an exception instead?
}

And now I rewrite the code like this

public static Integer sumList(List<Integer> integerList) throws InputException{
if(integerList.isEmpty()) {
throw new InputException("You can't sum an empty list, dummy!");
}
Integer sum = 0;
for(Integer element : integerList){
sum += element;
}
return sum;
}

Abstractions are hard. And each public function you write is an abstraction that could potentially be used by multiple people. Writing Unit Tests like this help you reconsider your abstraction and write better ones.


And this is not the only way a Unit Test helps the developer. Some other points to consider:-

  • It's good documentation:- JavaDocs can be unintelligible. Don't understand how to use some API? Look at the test cases for the API.
  • It's an easy way to run this small part of the application while developing.
  • It can be used like a "Confirm" button. You're breaking the way this abstraction works, are you sure?!
  • It reduces what I call "first run anxiety". Some features are large. You've already written 300 lines of code, was any of it even correct? Does it follow your mental model of what should happen?

Not All Unit Test Are Good Though

I do understand where the tweet comes from. When you're writing React code, it's easy to write bad tests. I'm guilty of it too. For example, when coding a Form in, you might write something like this:

class ExampleComponent extends React.Component {
// This helper is called onChange in the form fields.
function someHelper(type, value) {
switch(type) {
case INPUT_ONE:
this.setState({input_one_state: value});
break;
case INPUT_TWO:
// you get the idea
}
}
}

A natural first instinct is to try and call someHelper() and then inspect the state. I did this! Tools like Enzyme make this super easy. These are horrible tests.


React, in my opinion, breaks the mental model that most developers are used to. In Java, it is easy to test what the user uses. In the sumList example, users are using the function as is. In contrast, the user never uses React code. It is the DOM that is being used, and thus it is the DOM that needs to be tested. I had to adjust my mental model to think that React compiles into HTML, and that compiled output has to be tested.


React's state is the equivalent of a for-each loop in Java. If I switch it out for a while loop, it should not matter. All Enzyme allowed me to do was check whether I was using a for-each loop or not.


That's why react-testing-library is awesome. It reminds you that in the end, what matters is the DOM. So test that. All it provides are easy ways to find your element in the DOM, and trigger user events on them.


The Bad Unit Tests smells in Java

So, how do you spot a bad unit test in Java? Well first, don't just scroll through the unit test code in code reviews! Read them. You could spot a bad API too.


Also, consider these general rules:-

  • Do not mock/spy the class under test. It's easy to go wrong. If you're using a spy, it's likely that your class is too powerful.
  • Don't use PowerMockito, unless you have NO other choice.
  • If you're using a framework, Google to check if they provide utilities to test them.
  • Do not use mocks while testing your database layer. Use Test Containers.
  • Do not use mocks while testing REST endpoint clients. Use mock web servers.
  • After writing the test, check, are you mocking the main functionality of it? The test is useless if you are.

Impact Of Unit Tests

As I've mentioned before, Unit Tests have little value to the application. They exist to help developers correct themselves. So if you come across a bad unit test case, delete it. It makes no difference either way.


On the other hand, good unit tests speed up development. It helps you correct your mental model. It gives you confidence. And most importantly, it helps you think like the client.

So What Did I Teach The Freshers

Well, I definitely hope I taught them something useful. This was my first teaching experience! I had only only an hour to teach them the basics of Unit Testing. I'm glad I took it up, I got a blog post out of it!


Here's a link to the slides I made. Hit me up on twitter if you ever use them!