The 5 Java Testing Libraries Every Developer Should Know

Introduction Here at Dreamix often we need to be able to work on the overall quality of the software that we are producing. This includes not only creating java web applications with Angular and Spring but also our ability to take care after the code we have created, test it, automate the tests and verify the integrity […]

by Martin Patsov

March 3, 2020

7 min read

photography of person typing 1181675 scaled 1 - The 5 Java Testing Libraries Every Developer Should Know

Introduction

Here at Dreamix often we need to be able to work on the overall quality of the software that we are producing. This includes not only creating java web applications with Angular and Spring but also our ability to take care after the code we have created, test it, automate the tests and verify the integrity and desired behavior of the end product before it reaches the end-users.

To become a real java testing Jedi one needs to master the 5 java testing libraries that every developer should know.

So it goes.

The 5 java testing libraries

1 JUnit

Purpose

The purpose of JUnit usually is to enable you to create unit tests. Unit tests validate a specific small unit in your application in an isolated manner in relation to the behavior of your whole application.

For example, you could be testing only a specific public function in your class. I said usually because you could also use it to make way more complex testing scenarios. And while this is a possible application it is not its intended purpose. JUnit enables developers to execute TDD (test-driven development) which is a crucial part of the extreme programming methodology. For more information please check the reference source.

Pros

  • Easy to understand and use API
  • The nice learning curve, you are writing pure java code
  • Unit tests are fast and allow you to refactor confidently when doing TDD
  • Earlier detection of bugs and thus we have lowered costs of bugs
  • Improved design as a result of testable code
  • Big community and a lot of information on the internet

Cons

  • Unit tests cannot/should not be used to validate your whole application behavior and the integration between different components and/or external dependencies
  • You need Mockito in order to be able to do advanced unit testing, JUnit is not totally self-sufficient from this point of view

Code snippet example

Class under test: 
public class JUnitExample {
    int multiply(int a) {
        return a * 5;
    }
}
Test example:
public class JUnitExampleTest {
    @Test
    public void multiply_shouldReturnWrongResult_whenMultiplicand_isCorrect() {
        // given
        JUnitExample objectUnderTest = new JUnitExample();

        // when
        int result = objectUnderTest.multiply(2);

        // then
        assertEquals(10, result);
    }
}

 2 Mockito

Purpose

The purpose of Mockito is to enable you to create mocks and spies of your dependencies. Mocks and spies are also commonly referred to as stubs. Mocks are skeletons of real java objects i.e. dummy objects. Mocked objects are used in order to isolate your tests so that your unit tests are testing only the very specific unit of your application that you intend to test.

For example, you could be mocking an autowired field so that you imitate the behavior that the autowired object has in the real application. Mockito allows you much more than just mocking objects – you could be spying on the execution of real objects, you could be injecting mocks in other objects, etc.

Pros

  • Relatively easy to understand and use API
  • Combines very well with JUnit
  • A bit steeper learning curve compared to pure JUnit, especially if you are still trying to understand why and when you should use Mockito
  • Enables for better unit tests by providing isolation of dependency objects’ behavior
  • Big community and a lot of information on the internet

Cons

  • It takes some time to understand the concepts and start following the best practices
  • Unit tests cannot/should not be used to validate your whole application behavior and the integration between different components and/or external dependencies

Code snippet example

Class under test: 
public class MockitoSpyExample {
    int high(int fingers) {
        return getFingers(fingers);
    }

    int getFingers(int fingers) {
        return fingers;
    }
}
Test example:
public class MockitoSpyExampleTest {
    @Test
    public void high_shouldHaveDifferentFingers_whenMockitoSpyExampleResult_isAltered() {
        // given
        int expectedFingers = 10;
        MockitoSpyExample objectUnderTest = spy(new MockitoSpyExample());
        when(objectUnderTest.getFingers(any(Integer.class)))
                .thenReturn(expectedFingers);

        // when
        int receivedFingers = objectUnderTest.high(5);

        // then
        assertEquals(expectedFingers, receivedFingers);
    }
}

3 Hamcrest

Purpose

Hamcrest allows for creating advanced matchers for the assertions of results in our tests. Additionally to that, the matchers API is written in such a way that makes the tests more readable and the assertions become a lot easier to read and understand.

Examples of matchers include the size of collections, contents of collections, comparisons, string matchers and more. It even enables you to create your custom matchers!

Pros

  • Rich and intuitive API
  • Combines very well with JUnit and Mockito
  • Nice learning curve
  • Makes your tests more readable
  • Enables you to make your code shorter and more concise
  • Allows you to write your custom matchers
  • Big community and a lot of information on the internet

Cons

  • Yet another library to learn

Code snippet example

Class under test: 
public class HamcrestExample {
    int makeMillions(int amount) {
        return amount * 1_000_000;
    }
}
Test example:
public class HamcrestExampleTest {
    @Test
    public void makeMillions_shouldNotBecomeNegative_whenPositiveValue_isPassed() {
        // given
        HamcrestExample objectUnderTest = new HamcrestExample();

        // when
        int millions = objectUnderTest.makeMillions(5);

        // then
        assertThat(millions, is(not(lessThan(0))));
    }
}

4 PowerMock

Purpose

PowerMock provides functionality for testing and mocking static methods, private methods, final methods, accessing non-accessible members of classes and other dangerous things by bytecode manipulation and reflection.

PowerMock enables your tests to execute things that most of the time you should not need to be doing. Please handle with care and consider twice if what you are doing is right or there is something wrong with the design/architecture of your production code that you need to address first.

Pros

  • Rich API, resembling the one of Mockito, thus easing adoption
  • Combines very well with JUnit and Mockito
  • A bit steeper learning curve compared to JUnit and Mockito, due to the specifics in the purpose of use
  • Enables you to do advanced manipulations over objects
  • Allows you to bypass encapsulation which could be very useful in cases of bad design of (legacy) production code that is not easily testable
  • Uses a custom classloader which simplifies adoption (see the reference below)
  • Big community and a lot of information on the internet

Cons

  • A bit more complicated API
  • The need to use it may be an indication for bad design
  • Mainly intended for people with expert knowledge in unit testing, otherwise, it may cause more harm than good

Code snippet example

Class under test: 
public class PowerMockExample {
    public static int staticMethodThatWeWillMock() {
        return 5;
    }
}
Test example:
@RunWith(PowerMockRunner.class)
@PrepareForTest({PowerMockExample.class})
public class PowerMockExampleTest {

    @Test
    public void staticMethodThatWeWillMock_shouldReturnDifferentValue_whenReturnValue_isPowerMocked() {
        // given
        PowerMockito.mockStatic(PowerMockExample.class);
        PowerMockito.when(PowerMockExample.staticMethodThatWeWillMock())
                .thenReturn(200);

        // when
        int actualResult = PowerMockExample.staticMethodThatWeWillMock();

        // then
        assertEquals(200, actualResult);
    }

    @Test
    public void staticMethodThatWeWillMock_shouldReturnProperValue_whenReturnValue_isNotPowerMocked() {
        assertEquals(5, PowerMockExample.staticMethodThatWeWillMock());
    }
}

5 Selenium (WebDriver)

Purpose

Selenium automates the testing of web applications. It stimulates the interaction of a real user with our website. This means that we are able to create automated tests that verify the interaction between all of the components in our application just like it will be in the real “production” world.

There is also an option that a headless browser is used where the interactions occur “invisibly”, without a GUI (graphical user interface). No GUI also means faster execution time. You can also switch between different drivers for a different executing browser e.g. Google Chrome, Firefox, Safari, etc.

Pros

  • Provides close to real-world scenario integration testing
  • Enables validating the consistency of the behavior of our whole application
  • Rich API with a lot of functionality
  • Flexibility in browser driver
  • Headless option for browser execution
  • Developers prefer Selenium with java because of the rich and experienced ecosystem

Cons

  • The steep learning curve, it takes some time to set up and learn how to use properly
  • It is slower, compared to unit tests, in fact, the UI tests are the types of tests that take the most time and are most expensive to run (please check The Practical Test Pyramid from Martin Fowler)

Bonus

If you are looking for a way to improve the quality of your code you could use the help of a static code analyzer. The current state of the art analyzer according to me is SonarQube.

I have found it helpful in my work because it has all of the best practices “embedded”. It is like having the experience of a senior software developer applied instantly to you. It is really beneficial, especially for junior developers.

SonarQube empowers all developers to write cleaner and safer code.

Notable Mentions

We already listed my top 5 libraries, but there are two more that didn’t make the list but are worth mentioning.

AssertJ

AssertJ enhances unit testing and assertions add more asserting functionality and makes the assertions more readable. You could be interested in checking the method “assertThat()”

Apache JMeter

Apache JMeter allows us to do integration testing, load test functional behavior and measure the performance of our applications. In my work, I have found it useful to test the REST endpoints of a running application by creating HTTP requests.

Summary

To sum up, this is my list of 5 libraries every developer should be familiar with: JUnit, Mockito, Hamcrest, PowerMock, and Selenium. They allow for both unit testing and integration testing on the different levels of The Practical Test Pyramid.

A good static code analyzer that you could use is SonarQube. You could also enhance your unit tests by using AssertJ and your integration and load tests with Apache JMeter. You can find the GitHub repo with all of the examples here.

Conclusion

We could say that if one masters all of the 5 listed libraries he will have control over a force that many before have proven to prevent all hell from breaking loose. And this is something that every stakeholder, product owner, team leader, QA, DevOps, developer, etc. always want.

I will leave you with a question that I hope will make you think for a few minutes – as Uncle Bob Martin often likes to ask: “Can you trust your tests with your life?”

 

Categories