Monday, July 26, 2021

JUnit Testing Tips - Constructor is Called Before Executing Test Methods? Example

Even though almost all Java programmers either use JUnit or TestNG for their unit testing need along with some mock object generation libraries e.g. Mockito, but not everyone spends time and effort to learn subtle details of these testing libraries, at least not in proportion to any popular framework like Spring or Hibernate. In this blog post, I am sharing one of such detail, which has puzzled me a couple of years ago. At that time, though I had been using JUnit for a significant time, I wasn't aware that code written inside the constructor of the Test class is executed before each test method.


This behavior of JUnit has caused, some of my tests to failed and putting hours of investigation in my code, without realizing that this is happening because JUnit is initializing the object by calling constructor before executing test method annotated with @Test annotation.

I had the following code to test my vending machine implementation as a coding exercise. If you look closely, I have an initialized vending machine in the class body, which is executed as part of the constructor.

I was assuming one instance of the vending machine is shared between all test methods, as I was not using @Before and @After, JUnit 4 annotation for setup() and tearDown().

In the first test, one item from the Inventory is consumed and in the second test another item, but when you assert the count of items based upon the previous test, it will fail, because you are testing a different vending machine instance, which has different inventory.

So always, remember that JUnit calls the constructor of the test class before executing the test method. You can verify it by putting a System.out.println message in the constructor itself. Btw, if you are just starting with JUnit or have used it quite a long ago, then I suggest you to first refresh your concepts by going through the JUnit and Mockito Crash Course.

JUnit Testing Tips - Constructor is Called Before Executing Test Methods


JUnit has already come a long way with enhancements in JUnit 4.0 and JUnit 5.0, the latest release of JUnit.



Code Written in Constructor is Executed before each Test Method

JUnit Tips for Java ProgrammersHere is a code example of a JUnit test, which will demonstrate this point.  In our JUnit test class VendingMachineTest we have two test methods, buyDrinkWithExactAmount() and buyDrinkWithExactAmount(), and we are printing a message from a constructor.

In the output section, you can see that message from the constructor has appeared two times, one for each test case. This proves that the constructor of the JUnit test class is executed before each test method.


import java.util.List;
import org.junit.Test;
import static org.junit.Assert.;

/**
  *
  * @author Javin
  */
public class VendingMachineTest {
    private VendingMachine machine = new VendingMachine();
  
    public VendingMachineTest(){
        System.out.println("JUnit Framework calls Constructor of test class before executing test methods");
    }

    @Test
    public void buyDrinkWithExactAmount(){
        int price = machine.choose(Item.COKE);
        assertEquals(70, price);
        assertEquals(Item.COKE, machine.currentItem);
      
        machine.insert(Coin.QUARTER);
        machine.insert(Coin.QUARTER);
        machine.insert(Coin.DIME);
        machine.insert(Coin.DIME);
        assertEquals(70, machine.balance);
        assertEquals(7, (int) machine.coinInvertory.getCount(Coin.DIME));
        assertEquals(7, (int) machine.coinInvertory.getCount(Coin.QUARTER));
      
        Item i = machine.dispense();
        assertEquals(Item.COKE, i);
        assertEquals(4, (int) machine.itemInvertory.getCount(i));
      
        List change = machine.getChange();
        assertTrue(change.isEmpty());
      
      
    }
  
    @Test
    public void buyDrinkWithMoreAmount(){
        int price = machine.choose(Item.SPRITE);
        assertEquals(90, price);
        assertEquals(Item.SPRITE, machine.currentItem);
      
        machine.insert(Coin.QUARTER);
        machine.insert(Coin.QUARTER);
        machine.insert(Coin.QUARTER);
        machine.insert(Coin.QUARTER);
      
        assertEquals(100, machine.balance);
        assertEquals(9, (int) machine.coinInvertory.getCount(Coin.QUARTER));
      
        Item i = machine.dispense();
        assertEquals(Item.SPRITE, i);
        assertEquals(4, machine.itemInvertory.getCount(i));

        //this was failing, because by default VM initialize inventory with 5 items
        assertEquals(4, machine.itemInvertory.getCount(Item.COKE));

      
        List change = machine.getChange();
        assertEquals(1, change.size());
        assertEquals(Coin.DIME, change.get(0));
        assertEquals(4, machine.coinInvertory.getCount(Coin.DIME));
             
    }

Output:
Testsuite: test.VendingMachineTest
JUnit Framework calls Constructor of test class before executing test methods
JUnit Framework calls Constructor of test class before executing test methods
Tests run: 2, Failures: 1, Errors: 0, Time elapsed: 1.421 sec

Result
------------- ---------------- ---------------
Testcase: buyDrinkWithMoreAmount(test.VendingMachineTest):      FAILED
expected:<4> but was:<5>
junit.framework.AssertionFailedError: expected:<4> but was:<5>
        at test.VendingMachineTest.buyDrinkWithMoreAmount(VendingMachineTest.java:63)

That's all guys. I feel this is worth knowing in detail if you are using JUnit for writing a unit test in your Java project. Sometimes, we have a bug in the unit test itself, but we suspect our code. So it must for all Java developers to know the nitty-gritty of the JUnit testing framework as well.

Thanks for reading this article so far. If you like these JUnit tips then please share with your friends and colleagues. If you have any questions or feedback then please drop a note.

3 comments :

Anonymous said...

Well I am confused about how did your test fail? Each testcase should be an isolated entity and if that were the case, the test case would have never failed as each testcase would be given a new vendingMachine to perform the test.

Anonymous said...

As mentioned above, part of unit test paradigm is that each test should be isolated from the other.
If one test affects another, the tests design is wrong.

Having said that, if you do want a shared service for all your tests, which will be initialized only once (DB connection?), you can use the @BeforeClass annotation on a static method.

BTW,
I have never used constructor in Test class, unless I use JUnit parameters.

rop said...

I just felt you are not spelling out the complete story...

Yes, JUnit "calls the constructor" before each test, but that is only because it is actually creating a separate instance of the whole test-class for each method to be called.

So if you have, for example, a test-class with 5 test-methods, it will create 5 separate instances of the test-class and invoke only one method on each test-class.

You can easily verify this, by printing the value of: this.hashCode()
in the constructor, and in each test-method to see exactly what is going on.
It will show you that each test-method is invoked on a different instance of the test-class.

I guess it is implemented this way, to guarantee that the test-methods are as isolated and independent as possible from each other.

Also, per simple experiments, in JUnit 3.8, ALL the test-class instances are created BEFORE all the test-methods are invoked,
while in JUnit 4.11, it will create one instance and call the test-method, then create the next instance and call the test-method, etc,
(which is better, of course, since it requires less total memory-resources).

Post a Comment