RSS

Unit Testing Basics

23 Mar

Episode 62

Previous episode was about talking to database in Spring. Today we will continue exploring basics of building web applications, and talk about unit testing. This time, however, we are going to leave Spring alone for a moment and focus on plain unit tests using JUnit, Mockito and DataProvider.

4d821599ecc582a1ab14fe16e0594e27

As usual, all the code presented in this article is available on my GitHub in the same project as before. This tag corresponds to project state at the time of writing this article. Let’s start with some basic ideas, and why write unit tests in the first place.

Client does not pay for unit tests

Well, I have heard that once. I was in a project where code coverage was around 2%, and when I was actively trying to improve upon that, manager took me to a one on one discussion to a small room. Starting to cover old projects with tests is often difficult, but in the end the business pays much more for not having decent automatic test harness than saving on skipping it and only producing features. It simply pays back in the long run. There are less unnoticed bugs, design is cleaner, the amount of tedious repetitive work is dramatically reduced. Often it is much faster to develop with test, if we can run piece of code in isolation in seconds instead of restating big monoliths over and over, which is sometimes unavoidable without unit tests. Enough chit chat, let’s get down to code.

First Example

We will start with creating a simple service class that will count number of words in a given sentence and lengths of particular words. First method will return single number, second collection with lengths.

@Service
public class WordCounter {

	public int countWords(String sentence) {
		return sentence != null ? sentence.split(" ").length : 0;
	}

	public List<Integer> countLetters(String sentence) {
		List<Integer> result = new ArrayList<>();
		if (sentence != null) {
			for (String word : sentence.split(" ")) {
				result.add(word.length());
			}
		}
		return result;
	}
}

Normally it will be part of something bigger, but we want to test this particular component. In order to do that, we should create a class in the same package as test subject, but as part of src/test/java instead of src/main/java path. Traditionally test classes are named the same as the class they are testing, but with Test suffix.

public class WordCounterTest {

	private WordCounter wordCounter;

	@Test
	public void testWordCounting() {
		assertEquals(2, wordCounter.countWords("Hello world!"));
	}

	@Before
	public void setup() {
		wordCounter = new WordCounter();
	}
}

Each test is a method marked with @Test annotation. First, we need to prepare the environment. The method annotated with @Before will be run before each test case. We want a fresh start, so we construct our test subject every time. Then we provide arguments specific to test case and execute code. Finally, we are inspecting if results match our expectations using assertEquals method. To run the test, simply right click on the class and select something like “run as Junit test”, depending on your IDE. You should get a (short) list of executed test cases, hopefully all green.

laboratory_by_insolense-d62k2n3.jpg

Data Provider

So far, so good. But what if we want to test several sets of arguments in one go, just to be sure? We can add multiple asserts, but cleaner solution is to use DataProvider mechanism. Let’s add a dependency to our pom.xml.

<dependency>
	<groupId>com.tngtech.java</groupId>
	<artifactId>junit-dataprovider</artifactId>
	<version>1.10.0</version>
	<scope>test</scope>
</dependency>

To make things work, we will also need a @RunWith(DataProviderRunner.class) annotation at our test  class. Now we can add following test case:

@Test
@DataProvider({ "2,Hello world!", "1,hi" })
	public void testWordCountingWithData(int words, String sentence) {
		assertEquals(words, wordCounter.countWords(sentence));
	}

@DataProvider comes by default with an array of Strings that contain list of coma separated arguments for subsequent test method executions. Notice, that now we have two arguments in the method: expected number of words and a sentence. When we execute that, we will see a test case for each set of arguments we have provided. What if there is a comma in the damn sentence?

	@Test
	@DataProvider(value = { "3; one, two, three" }, splitBy = ";")
	public void testLetterCountingWithComas(int words, String sentence) {
		assertEquals(words, wordCounter.countWords(sentence));
	}

We can simply provide another separator as annotation parameter.

Matchers

Sometimes, we want more elaborate inspection. We can write more elaborate piece of code for that, or just use one of many Hamcrest matchers. Let’s test our method for counting letters in particular words in a sentence:

@Test
public void testLetterCounting() {
	List<Integer> result = wordCounter.countLetters("Counting letters in words");
	assertThat(result, hasSize(4));
	assertThat(result, contains(8, 7, 2, 5));
	assertThat(result, hasItem(7));
	assertThat(result.get(0), equalTo(8));
}

We use assertThat method with conjunction of inspected object and so called matcher, that can check various properties of an object in short and elegant manner. Check full list here.

9472a08587c01043023988df81737e34.jpg

Mocking

WordCounter was simple and dealt with its problem by itself. What if we rely on other objects to do the job? The goal of unit test is to test single class and assume that all dependencies are ok. In order to achieve that, we can mock them – use artificial versions that are told what to do under certain circumstances. Let’s add new class, WordService, that will use our WordCounter.

@Service
public class WordService {

	@Autowired
	private WordCounter wordCounter;

	public int countWords(String sentence) {
		sentence = sentence != null ? sentence.trim() : "";
		return wordCounter.countWords(sentence);
	}
}

To test this, we will mock WordCounter and tell it what to do when invoked.

public class WordServiceTest {

	@InjectMocks
	private WordService service;

	@Mock
	private WordCounter wordCounter;

	@Test
	public void testCountWords() {
		assertThat(service.countWords("hello there"), equalTo(2));
	}

	@Before
	public void setup() {
		MockitoAnnotations.initMocks(this);
		when(wordCounter.countWords(any())).thenReturn(2);
	}

}

Fields marked with @Mock will be populated with mocked object’s versions. Fields marked with @InjectMocks will be assigned with real object. The real object’s field will be assigned matching mocked versions. To make things happen we need to call initMock. To tell the mock what to do, we use when and then methods plus a matcher object that cheks arguments passed to mock object by the real object. If they match, the defined action is performed.

Argument Capture

Sometimes, we would like to check what arguments were passed to certain internal objects in our class. In our case, we trim the sentence in WordService before sending it to WordCounter. To see if it worked, we can use argument captor.

@Test
public void testArgumentCapture() {
	service.countWords("  hello there  ");

	ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
	verify(wordCounter, times(1)).countWords(captor.capture());
	assertThat(captor.getValue(), equalTo("hello there"));
}

Captor object is parametrized with class of the argument it is intended to capture. First, we execute our tested method, then we can use verify to populate our captor with invocation arguments and check if there was right number of invocations using methods like times(n), atLeast(n), atMost(n). Then we can obtain argument or arguments list used and check if they are what we expect.

Tips

When writing unit tests it’s good to remember about few things:

  • Use code coverage tool to make sure all relevant paths in code are taken care of.
  • High coverage does not automatically imply good tests, but if coverage is low, tests are most likely inadequate.
  • Test few “green paths” and then look for possible problems that might happen: null values, unexpected values (like negative person age), exceptions thrown by dependencies.
  • Test corner cases, maximum and minimum allowed values, empty arrays and collections, empty strings, white spaces etc. Trust no one.
  • Each test should be independent and their run order should not matter.
  • Test one specific behavior of a class and name the test accordingly.
  • Tests are code, the same as the production one, so keep it clean and refactor if needed. No excuses.

There is also a matter of testing database access and web controllers, but those are topics for another article.  Happy testing and see you next time :)

p.s. I’ve added a Subscribe widget, just below the Toothless picture on the right side. You are welcome to leave me your email, so I can remind you about new episodes ;) No spam, promise.

Screen Shot 2014-12-18 at 10.14.02 AM.png

Image sources:

 
3 Comments

Posted by on March 23, 2017 in Clean Code, Spring, Technology

 

Tags: , , ,

3 responses to “Unit Testing Basics

  1. Michał

    March 23, 2017 at 9:09 pm

    Hey man, great tutorial :) However in paragraph about DataProvider, maybe You can add information about @RunWith(DataProvider.class), which is required to run these tests correctly ? :)
    Best regards, keep goin with Your tuts :)

    Like

     
    • gvaireth

      March 24, 2017 at 7:27 am

      Yes, I forgot about that. Thanks for the hint ;)

      Like

       

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

 
%d bloggers like this: