Phat Nguyen
Phat Ng

Phat Ng

Steps to writing a lovely unit test method

Steps to writing a lovely unit test method

Phat Nguyen's photo
Phat Nguyen

Published on Oct 16, 2021

4 min read

When you implement a service, you probably be asked to make sure that your implementation will be working at a certain level or at least can run on production. The solution is we write other methods (which only run in the test environment) to prove our implementation can work well, that process is called writing unit tests.

Unfortunately, before I actually took a developer job, I had not touched any unit test method. Even though I have made a lot of projects (which were graded for the subject at university), I try to code clean, easy to scale but in the end, still there is no line of a unit test. So when I was asked to write a unit test, I was very confused because I have no experience with it, but spending hours collecting all things on the internet, I was able to write a runnable unit test.

In this article, I'll wrap all my knowledge about writing unit tests that can help you, the beginner, save a lot of searching hours. Let's get started!

What is unit testing?

The definition I found on Wikipedia states that:

unit testing is a software testing method by which individual units of source code are tested to determine whether they are fit for use.

Sounds very clear but I also have my own definition:

Unit testing is the ability by which, you can prove that your method will work perfectly in reasonable scenarios.

So, how can we make that ability? By writing some methods - unit tests.

How to write (lovely) unit tests?

Beforehand, I am going to use Spring Boot for writing service methods that need to be tested and standard maven dependency which provides unit testing in Spring Boot.

The following is my service method:

@Override
public List<Book> getAll() {
    return bookRepository.findAll();
}

@Override
public Book getOne(Long id) {
    return bookRepository.findById(id)
               .orElseThrow(BookNotFoundException::new);
}

Right here, I have 2 methods that get some data from their repository, now take a look at the following unit test for the getAll method.

@Test
void whenGetAll_shouldReturnList() {
    // 1. create mock data
    List<Book> mockBooks = new ArrayList<>();
    for(int i = 0; i < 5; i++) {
        mockBooks.add(new Book((long)i));
    }

    // 2. define behavior of the repository
    when(bookRepository.findAll()).thenReturn(mockBooks);

    // 3. call the service method
    List<Book> actualBooks = bookService.getAll();

    // 4. assert the result
    assertThat(actualBooks.size()).isEqualTo(mockBooks.size());

    // 4.1 ensure the repository is called
    verify(bookRepository).findAll();
}

As you can see, we have 4 steps but before exploring each step we’re going to see how to name a unit test. There is no strict rule for a good unit test name but defining a readable name can help you and others reviewing those codes can quickly catch up with what you have implemented. My name's rule is:

  • For methods that do not have any argument, the name should consider 2 parts: the name of the method being tested and the expected behavior.
  • For methods that have arguments, also have 2 parts: the scenario (input) under which it’s being tested and the expected behavior.

So the name whenGetAll_shouldReturnList uses my rule one, the name of the method (getAll) and return a list is our expected behavior.

Back to the 4 steps in the unit test body, I think it’s clear enough to understand, here is the summary:

  • Step 1: Prepare our mock data, since we don’t actually touch any data in a database (just testing the method flow), we must dump some data which the repository will return.
  • Step 2: Assume the repository behavior, as I mentioned in the previous step, we have no connection with a database so when the repository method is invoked, it can use our mock data to return.
  • Step 3: Invoke the method that is being tested, we will receive the result that can make further assertions.
  • Step 4: Assert the result with the expected one to make sure we implement the right flow in the method. We also have an extra step (4.1) that verifies the repository is invoked 1 time (when we invoke the service method).

There is one service method left that needs to be tested, but I will let you do it and check against my implementation. Drop your implementation in the comment section and I’m pleased to learn new things from you 😀

Finally, the green tick comes to show your effort is deserved. 🤗🎉🎉

blog-2_w6dpts.png

You can find the entire source code in my Github.


This is the first time I write an entire post in English, the incorrect grammar or vocabulary may appear a lot, but I'm not embarrassed about it, I truly appreciate that you can point out and correct my mistakes. Thank you for taking the time to read with me.

Happy coding!


 
Share this