Unit Testing: Warm-up.

Owais Idris
10 min readSep 2, 2019

for beginners

Take away

Little flavor of test.
Mocking Dependency
Mocking Behaviour
What should be my direction?

Often a newbie don’t understand the value of test because there are so much more that he wants to learn. He somehow will be able to put together all his initial learning and create some production ready code. Learning from seniors or web. He has some time to play with and get the work done.

I hope most of you are coming from that backyard. After some experience, writing a complex feature and then getting it run in a single go is an achievement. Many of us have seen that phase too.

Then?

Features keeps changing and new features are shipped regularly. But with time thing gets interdependent and more tangled. Control over some areas get fuzzy. Now adding one more feature or a change is mind bending. Product Manager is already demanding. There is a wild need to ship even more features. Timelines are tight and estimates are going wrong. We arrive at a point where we are blanking out on our own code.

That’s where your test can save you.

Writing a Test

In this article I am not going to go deep into concepts or dive into any kind of code. We are going to understand where modern testing stands. What are the features that are common among them. How a developer who writes test should think? What tools you must search in regards to whatever technology you are working on?

Here are some per-requisites you must check in your own development environment.

Per-requisites

IDE

Do you have an IDE that behaves like mine? You create a new instance or declaration of variable of a class or interface that actually doesn’t exist and your IDE prompt error that it doesn’t exist. Does it gives you prompt or option to create one from there, with some simple clicks. Yes we will need this kind of an IDE.

One at a time

Test Driven Development demands us to declare what doesn’t exist. Then later think how to implement it. It is about having just a rough idea to think what kind of class is needed and what a class must behaves like. Every class must do exactly one thing. All you need to identify and narrow down to just one class at a time.

For example if you are making an online shopping cart feature, then one class should be responsible to collect/register items and other class should be responsible for checkout process.

class Cart{fun addToCart(Item item){...}
fun removeFromCart(String id){...}
fun getItems(): List<Item> {...}
}
class Checkout{
fun checkout(Cart cart){}
}

Tools and Libraries

There are some common requirements which are commonly seen in test codes out there. Lets not jump to those libraries instead just understand what kind of tricks are used to make test work.

Now, Object Oriented Programming is all about communication between 2 objects with the help of messages shared. So testing a single class without collaborators to collaborate is unimaginable. They might not even exist at this point, when you are creating and testing your class. This is going to be your first challenge when you are testing. There are many tools and library to help you, so before you re-invent the wheel go and find them on Internet/Github etc. Now lets see what they are…

Test with Mock

Consider you are testing a class which does not exist. You start writing your first test and on error you create class. But then your class deals with some other class/interface and even that is not existing. Then what? Simple, just declare them and on error create them. Lets look at one example.

//Step One when checkout doesn't exist
class CheckoutTest{
fun classShouldExist(){
Checkout();//This will give error Class Not Found
}
}

Assume now you have created a class. And Checkout must be able to checkout items. In that process it should call an API to invoke payment service and do couple of update. We don’t know how all that stuff are going to work but all we could imagine is that there must be a Repository at other end which encapsulate everything we needed for API calls.

Even this repository doesn’t exist. We are going to follow same steps, declare it and when an error prompts create it.

//Step when dependency doesn't exist
class CheckoutTest{
CheckoutRepo mockRepo;//This will give error Class Not Foundfun classShouldExist(){
Checkout(mockRepo);//This will prompt you to update constructor
}
}

Once we have created CheckoutRepo it will be something like this.

interface CheckoutRepo{}//as we don't know any implementations

Now lets move to next step and test for checkout. Lets assume whenever Checkout calls checkout(cart) method, our repository should be invoked. For simplicity our List of item_ids are sent to server, to process. In that sense lets first update our test for checkout.

//Step Arrange Action Assert
class CheckoutTest{
CheckoutRepo mockRepo; fun classShouldExist(){
Checkout(mockRepo);
}
@Test //This is to mark method as test method *1.
fun testCheckout(){
//Arrange *2
Cart cart=new Cart();
cart.addToCart(new Item(id="1234", quantity=2);
Checkout checkout=new Checkout(mockRepo);

//Action *3
checkout.checkout(cart);
//Assert *4
}}

We know that we have just created CheckoutRepo so its ok that is not having any implementation. Repo is just an interface to which our test class will communicate and depend on. All we need to define here is what kind of methods do CheckoutRepo will expose to Checkout Class.

What is Repo? ; For those who don’t understand repo to keep it simple a repo is a class that communicates with Database or Remote API. These are library or vendor specific so we don’t test it(As vendor might have already tested it).

Its now time to fill that “Assert” step of our test. Here we want to verify that our repo is invoked by list of items that were added to cart. And that’s it.

//Step Arrange Action Assert
class
CheckoutTest{@Mock
CheckoutRepo repo;
fun classShouldExist(){
Checkout(repo);
}
@BeforeEach //it make sure this method is called before each test
fun setup(){
MockitoAnnotation.init()//
initialise Mockito instances
}
@Test //This is to mark method as test method *1.
fun testCheckout(){
//Arrange *2 Cart cart=new Cart();
cart.addToCart(new Item(id="1234", quantity=2);
Checkout checkout=new Checkout(repo);

//Action *3
checkout.checkout(cart);
//Assert *4
Mockito.verify(repo, only()).order(cart.getItems())
}
}

It is OK to be clueless here if you don’t understand some thing in above example. We have not tested anything very significant here. I told you we are going to narrow down the scope and test one at a time.

Explanations : Let me explain you above code.

I am using a Mocking library of my choice. It has its own way to mark variable that will be mocked through (@) annotations. We have already consumed that variable in arrange section and passed repo as a dependency through constructor.

Setup-Method
Before consuming mocked variable it is important to initialize those variables which in my case it done using MockitoAnnotations.init() method. I could have placed this as first line of my test method and skipped setup() method.

@Test
fun checkoutTest(){
MockitoAnnotations.init(repo);
}

This could serve the purpose. But when I will write next test which require again a mocked object, then I will have to repeated call MockitoAnnotations.init() again. For re-usability we have just moved that in a separate method.

@BeforeEach: This annotation is provided by my Test library. It ensure that any method annotated by this must be called before each test method is executed. There must be some more annotations like these in your test library too. To name some .. @BeforeClass @AfterEach @AfterClass etc.

Mockito.verify(repo, only()).order(cart.getItems())

My Mocking library also have a provision to check where there is any interaction with the mocked variable. If yes what kind of interaction? And how many times? And if there was any then what kind of message was share between objects(in parameters).

Method only() make sure that repo.order() is called exactly one time. There are other provisions to like atleastOnce(), time(n), never(). And this way I can check interactions of test class with its collaborator classes/interfaces.

You saw that even though there is not much happening here our intention is not to create code here. All we need to understand here is the potential tools that can be used.

A saw is used to cut trees and that same saw is used in the process of building a furniture. Both the cases Saw has only one responsibility but outcomes are totally different. Saw is just a tool, you are the fuel.

So don’t adhere to anything. In your test library you might have some other handy tools specific to your environment. Write enough test to create enough code, that satisfy a test.

After writing this in my IDE there is a green play button ▶ which when pressed, runs test. There is also one Play All Button ▶ which run all the test in my Test class. After you have press any of these button you will see your test running or there can be any one of two failures.

  1. Compile Time Failure: We might have missed any import or any silly mistake.
  2. Test Failure: You will be given a detailed message. As we have never created any implementation of Checkout.checkout() , possible error is there is no interaction with mocked object.

Passing a failing Test: Test should fail for the first time this will ensure that you are not testing it wrong. Then after implementation it must pass. This is called Red-Green test too. Failing test ensure that there won’t be any ambiguity introduced.

Now lets quickly implement checkout method.

Class Checkout{
...
fun checkout(cart: Cart){
repo.order(cart.getItems())
}

}

Cart does not make any sense to our Repo. Cart is our domain logic and just specific to current client. This Repo can be used with just another version of our app with some different concept. Hence repo has responsibility, to be an interface between data provider and consumer.

So far we have mocked the dependency, we can even mock behavior. In our example we assumed that Cart class exist and is already implemented thus we simply used it. This was for simplicity. I wanted to make you understand that mocking doesn’t mean temporary implementation. Temporary implementation is called Faking.

Faking: We already have Repo interface. It is our choice now whether we will mock it again in next test class or create a Helper class in test and use this fake Repo implementation. While faking we can choose a response from API already copied in some file, case by case. Our Repo, then instead of querying network it will read from this sample data. Hence it will be a more realistic test, with real data template.

Mocking Behaviour
If we assume that Cart.class don’t exist then we will have to mock it. So first lets mock it.

@Mock Cart cart;

Next, all we wanted from cart is “cart.getItems()” which will return a list of items. Item is a simple POJO class just holding data so we are not mocking it for time being and using it concretely. Lets not get carried away we are suppose to deal with Cart object now. Whenever cart.getITems() is called it must return list of items. And as I am mocking it, there must be some provision to do so in my mocking library. And that is what we are going to use.

@Mock Cart cart;@BeforeEach
fun setup()
MockitoAnnotations.init(this);
Mockito.`when`(cart.getItems()).thenReturn(listOf(Item(id="1234", quantity=2))
}

Above snippet does same. It return list of just 1 item when cart.getItem() is invoked. By now you can see, why I have updated setup() method?

Mocking a behavior have lots of benefits. It saves time and energy. But if there are too many interaction it would be wise to create a fake implementation. This will save you from redundant work of mocking behaviour and avoid confusion. You may create fake implementation based on cases. However it is very subjective how you choose when to mock and when to fake.

Wrapping up the test:

Arrange|Action|Assert these three A’s are your sections that keeps your test clean. In our test later we can also add other assertions like tax calculation , query repo for price of item then assert for desired result. Testing a checkout process may include querying price from repo, calculating tax and returning an order, Or lastly your API does everything so client must not bother. Assuming back-end developer have already tested it.

Let me show you little complex update to this same test.(Just look at the pattern)

@Mock OrderGenerator orderGenerator;
@Mock CheckoutRepo repo;
fun checkoutTest(){
//Arrange
...
Mockito.when(cart.getItems()).thenReturn(listOf(Items(id="1234", quantity=5)))}
val checkout:Checkout=Checkout(repo, ..., orderGenerator)
...
//Action
checkout.chekout(cart)
//Assert
assertEquals(.., ..)
assertTrue(.., true)
Mockito.verify(repo, only).placeOrder(fakeOrder)
Mockito.verify(orderGenerator, atleastOnce()).calculateTax(xyz)
Mockito.verify(orderGenerator, never()).applyDiscount()
}

All I want to explain here, my sole intent was to test checkout process which involved interaction with couple of collaborators.

  1. At first we tested one interaction. It failed and then we satisfied its dependency added implementation and finally test passed. But still checkout was not limited to that one interaction there were some more. So we updated that same test.
  2. We added dependencies, then after Action was performed we asserted them. Like a sketch we iterated our test by first adding minimum code and slowly adding more complexity. All of it but one by one.
  3. How can I know that there will be more interactions? How can I be sure about it? Well I knew nothing(like John Snow) I figured out along the way. In this process of creating checkout process I would have refactored my code number of times to get desired behaviour.

Initially I was just supposed to satisfy tests but then when found there is also a tax calculation mechanism, it was a hint that there must be some class who does that, OrderGenerator for now. Later if I feel OrderGenerator is just doing too much then I would move TaxCalculation to else where(say TaxMachine). TaxMachine given an input it will give some output, we can again write separate test for that.

Conclusion

There is no shortcut to success. This is not the end, if you are beginner this was just an effort to give you some flavor about test. Testing is incomplete without learning its philosophy and believing in Refactoring. Initially when we are introduced to testing we thought of architecture and what will be implementation before-hand. Well if you keep doing what you have been doing until now, undesired result is your fortune. Idea is to submit yourselves to test and be ready for refactoring. Once you are ready with these two thing you will start getting some grip of testing.

I would suggest to read some books on Testing And Refactoring. A book will clear all concepts in a consolidated way, reading random articles will never give you a clear consolidated picture. Then refer to testing libraries like JUnit4, JUnit5, Spek, KotlinTest. And Mocking Library like Mockito.

--

--