Wiring everything up for unit testing with Scalatest + Mockito + ArgumentCaptor
Unit testing should be one of the cornerstones of every serious developer. Any piece of business logic should be isolated and tested on its own, making sure that it works just as expected. However, setting up the required tools may be a bit tricky, so it’s way more common that it should to see crappy tests and untested code. I’ve spent quite some time googling how to join all the pieces and all I could find was partial and/or outdated information, so I though that a post with the whole setup would be useful.
Here I will explain how I approach my unit tests, detailing what every piece is for and how it relates to the others.
For this example, we’ll be testing a class that receives some input data from a person registering on a form, transforms it to our internal representation of client, and stores it.
This is the information received from the form
This is how we represent our clients.
As you can see, we will transform from age to isMinor. This is a very simple business rule but can serve as an example.
Our storage will be, for now, represented with a Trait that can be implemented later on. The only function, store, returns an Option depending on whether the client could be stored or not.
This is our core class, the one that we want to test. It has one method that tries to store a client and returns a Boolean to indicate if it was possible (I know this probably doesn’t make much sense, but it serves us to demonstrate how the testing libraries work)
Now, how are we testing this? We want to be sure that the business logic (lines 3 and 5) is correct, and both the values stored and returned are the ones we expect.
I will depict every piece independently, and then show the full code with all the required imports and boilerplate.
Here we are initializing the formProcessing class that will be tested. The storage is mocked, so we’ll need to specify its behavior on every test.
This is the first test. Here we want to test a piece of business logic: if the age introduced in the form is smaller than 18, then the stored client has isMinor set to true.
Line per line analysis:
1) test name and functionality
2–3) as the storage is mocked, here we specify what it should return when called.
5–6) call the class to be tested
7) variable used to capture the argument used to call the mocked storage
9) we do two things: capture the argument used for the store call, and make sure that the function was called one time
10) verification on the value of the captured argument: parameter isMinor has to be False
After this, all the business logic in the class can be tested with the same approach. The only problem to consider is that, on a second test, verify(storage, times(1)) will fail saying that the function was called twice. This is because the counter of invocations is not reset after each test. To do so, we need the following function to be executed before each test:
Altogether, this is our test class with all the wiring, imports, and everything needed. First Gist is for the business logic, second one is for the unit tests: