9

Writing Tests in Spring Boot

In the previous chapter, you have learned about the importance of loggers, their concepts, and how they can help developers debug and maintain applications. You have learned about Log4j2, which is a third-party framework for Spring Boot that offers several features such as Appenders, Filters, and Markers that can assist in making log events categorized and formatted for developers. We have also discussed SLF4J, which is an abstraction of logging frameworks that allows us to switch between different frameworks during runtime or at deployment, and lastly, we have implemented and configured the logging frameworks with XML configuration and Lombok.

This chapter will now focus on writing unit tests for our Spring Boot application; we will discuss the most commonly used testing frameworks with Java, JUnit, and AssertJ and implement them in our application. We will also be integrating Mockito with our unit test for mocking objects and services.

In this chapter, we will cover the following topics:

  • Understanding JUnit and AssertJ
  • Writing a test
  • Writing tests in a service using Mockito

Technical requirements

The link to the finished version of this chapter is here: https://github.com/PacktPublishing/Spring-Boot-and-Angular/tree/main/Chapter-09.

Understanding JUnit and AssertJ

After every development of an application, testing will always be the next step, and this is one of the most important tasks before delivering or deploying our application into production for the world. The testing phase is critical for companies, as this ensures the quality and effectiveness of their products.

As this is one of the essential processes, there should be little room for errors in testing, and manual testing is not enough, as this is prone to human errors and has a more significant chance of missing the existing issues in an application. This is where unit testing comes to the rescue – unit testing is automated testing that allows the developer to write tests for a single class or entity.

It is a form of regression testing that runs all of the tests to validate whether the code still passes the test cases after several changes or updates have been applied to the application code. Unit tests help maintain the quality of our applications, as they bring the following benefits:

  • Speed: Unit testing will be less time-consuming compared to manual testing, as this is programmable and will deliver the results in a short period.
  • Cost reduction: Unit testing is automated, which means fewer testers will be required for testing the application.
  • Fewer errors: Unit testing will significantly reduce the number of errors made, as testing is not done manually by humans.
  • Programmable: Unit tests can produce sophisticated tests that detect hidden information in the application.

Unit tests are widely used now in both frontend and backend development, especially in Java, because of their advantages and testing. There are already several testing frameworks available in Java, but we will discuss the first and most commonly used framework, JUnit.

JUnit framework

JUnit is a regression testing framework mainly used for writing tests and assertions for single classes in a Java application; it promotes the idea of first testing and then coding, which states that we need to create test data for a piece of code to be tested before implementation. JUnit is also an open source framework, which makes it more reliable.

There is a large community supporting the framework, it uses assertions to test expected results and annotations to identify the methods for testing, and it can be efficiently utilized and integrated with Maven and Gradle projects.

Let’s discuss the features of JUnit that we will use for writing tests:

  • Fixtures: These are objects that we can consider constants or the baseline for running tests. The primary use of fixtures is to ensure that variables that have the same value throughout testing will be used. There are two types of fixtures, and these are as follows:
    • setUp(): This method is executed before every test is invoked.
    • tearDown(): This method is executed after every test is invoked:
      public class JavaTest extends TestCase {
         protected int value1, value2;
         // will run before testSubtract and testMultiply
         protected void setUp(){
            value1 = 23;
            value2 = 10;
         }
         public void testSubtract(){
            double result = value1 - value2;
            assertTrue(result == 13);
         }
         public void testMultiply(){
            double result = value1 * value2;
            assertTrue(result == 230);
         }}

In the preceding code example, we can see that there are two test methods defined, which are testSubtract() and testMultiply(), before each method is called. The setUp() fixture will be called first to assign the values of the value1 and value2 variables.

  • Test suites: Group several unit test cases and execute them together, using the @RunWith and @Suite annotations to run the tests. Let’s have a look at the following example:
    //JUnit Suite Test
    @RunWith(Suite.class)
    @Suite.SuiteClasses({
       TestOne.class, TestTwo.class
    });
    public class JunitTestSuite {
    }
    public class TestOne {
       int x = 1;
       int y = 2;
       @Test
       public void TestOne() {
          assertEquals(x + y, 3);
       }
    }
    public class TestTwo {
       int x = 1;
       int y = 2;
       @Test
       public void TestTwo() {
          assertEquals(y - x, 1);
       }
    }

In the preceding code example, we can see that we have two defined classes with a method with the @Test annotation; the test methods will be executed together, as we have bundled them using the @Suite.SuiteClasses method.

  • Test runners: Mainly used for running the test cases, we use the runClasses() method to run the test cases inside a specific class. Let’s have a look at a basic example here:
    public class JUnitTestRunner {
       public static void main(String[] args) {
          Result result =
            JUnitCore.runClasses(TestJunit.class);
          for (Failure failure : result.getFailures()) {
             System.out.println(failure.toString());
          }
          System.out.println(result.wasSuccessful());
       }
    }
  • Classes: JUnit classes are mainly used for writing the tests for our application; these include the following:
    • Assert: Includes the set of assert methods
    • Test case: Includes the test cases that contain the fixtures for running multiple tests
    • Test result: Includes the methods to gather all of the results from an executed test case

Assertions in JUnit

Assertions are the way to validate whether our tests are valid by checking the outcome of the written code. In JUnit, all assertions are under the Assert class, and some of the essential methods from Assert are as follows:

  • void assertTrue(boolean condition): Validates whether the condition is true
  • void assertFalse(boolean condition): Validates whether the condition is false
  • void assertNotNull(Object obj): Checks whether the object is not null
  • void assertNull(Object obj): Checks whether the object is null
  • void assertEquals(Object obj1, Object obj2): Checks whether two objects or primitives are equal
  • void assertArrayEquals(Array array1, Array array2): Validates whether two arrays are equal to each other

Annotations

Annotations are meta tags that we add to methods and classes; this provides additional information to JUnit about which methods should run before and after the test methods and which will be ignored during the test execution.

Here are the annotations that we can use in JUnit:

  • @Test: This annotation is used for a public void method to signify that the method is a test case that can be executed.
  • @Ignore: This annotation is used to ignore a test case not being executed.
  • @Before: This annotation is used for a public void method to run the method before each test case method. This is commonly used if we want to declare similar objects used by all test cases.
  • @After: The annotation is used for a public void method to run the method after each test case method; this is commonly used if we want to release or clean several resources before running a new test case.
  • @BeforeClass: The annotation allows a public static void method to run once before all of the test cases are executed.
  • @AfterClass: The annotation allows a public static void method to run once all test cases are executed.

Let’s have an example test with annotations and their sequence of execution:

public class JunitAnnotationSequence {
   //execute once before all test
   @BeforeClass
   public static void beforeClass() {
      System.out.println("beforeClass()");
   }
   //execute once after all test
   @AfterClass
   public static void  afterClass() {
      System.out.println("afterClass()");
   }
   //execute before each test
   @Before
   public void before() {
      System.out.println("before()");
   }
   //execute after each test
   @After
   public void after() {
      System.out.println("after()");
   }
   @Test
   public void testMethod1() {
      System.out.println("testMethod1()");
   }
   @Test
   public void testMethod2() {
      System.out.println("testMethod2();");
   }
}

In the preceding code example, we have a JunitAnnotationSequence class that has several annotated methods. When we execute the test, we will have the following output:

beforeClass()
before()
testMethod1()
after()
before()
testMethod2()
after()
afterClass()

We can see in the preceding example that the methods annotated with @BeforeClass and @AfterClass are only called once and they are called at the start and end of the test execution. On the other hand, the methods annotated with @Before and @After are called at the beginning and the end of each test method.

We have learned about the basics of JUnit in unit testing; now, let’s discuss the concepts of AssertJ.

Using AssertJ

We have just explored the concepts and features of JUnit in the last part, and we have learned that in JUnit alone, we can apply assertions using the Assert class, but we can make our assertions more fluent and flexible by using AssertJ. AssertJ is a library mainly used for writing assertions; its primary goal is to improve the readability of test code and make the maintenance of tests simpler.

Let’s compare how to write assertions in JUnit and AssertJ:

  • JUnit checking whether the condition returns true:
    Assert.assertTrue(condition)
  • AssetJ checking whether the condition returns true:
    Assertions.assertThat(condition).isTrue()

We can see in the preceding example that in AssertJ, we will always pass the object to be compared in the assertThat() method, and we will call the next method, which is the actual assertion. Let’s have a look at the different kinds of assertions we can use in AssertJ.

Boolean assertions

Boolean assertions are used to check whether conditions return true or false. The assertion methods are as follows:

  • isTrue(): Checks whether the condition is true:
    Assertions.assertThat(4 > 3).isTrue()
  • isFalse(): Checks whether the condition is false:
    Assertions.assertThat(11 > 100).isFalse()

Character assertions

Character assertions are used to compare the object to a character or check whether the character is in the Unicode table; the assertion methods are as follows:

  • isLowerCase(): Reviews whether the given character is lowercase:
    Assertions.assertThat('a').isLowerCase();
  • isUpperCase(): Checks whether the character is uppercase:
    Assertions.assertThat('a').isUpperCase();
  • isEqualTo(): Checks whether the two given characters are equal:
    Assertions.assertThat('a').isEqualTo('a');
  • isNotEqualTo(): Checks whether the two given characters are not equal:
    Assertions.assertThat('a').isEqualTo('b');
  • inUnicode(): Checks whether the character is included in the Unicode table:
    Assertions.assertThat('a').inUniCode();

These are just some of the assertions available under AbstractCharacterAssert. For the complete documentation, you can go to https://joel-costigliola.github.io/assertj/core-8/api/org/assertj/core/api/AbstractCharacterAssert.html.

Class assertions

Class assertions are used to check the fields, types, access modifiers, and annotations in a specific class. The following are some of the class assertion methods:

  • isNotInterface(): Verifies that the class is not an interface:
    Interface Hero {}
    class Thor implements Hero {}
    Assertions.assertThat(Thor.class).isNotInterface()
  • isInterface(): Verifies that the class is an interface:
    Interface Hero {}
    class Thor implements Hero {}
    Assertions.assertThat(Hero.class).isInterface()
  • isPublic(): Verifies that the class is public:
    public class Hero {}
    protected class AntiHero {}
    Assertions.assertThat(Hero.class).isPublic()
  • isNotPublic(): Verifies that the class is not public:
    public class Hero {}
    protected class AntiHero {}
    Assertions.assertThat(Hero.class).isNotPublic()

These are just some of the assertions available under AbstractClassAssert. For the complete documentation, you can go to https://joel-costigliola.github.io/assertj/core-8/api/org/assertj/core/api/AbstractClassAssert.html.

Iterable assertions

Iterable assertions are used to verify an iterable or array object based on its length and contents. The following are some of the iterable assertion methods:

  • contains(): Demonstrates that the iterable has the given values:
    List test = List.asList("Thor", "Hulk",
                            "Dr. Strange");
    assertThat(test).contains("Thor");
  • isEmpty(): Verifies whether the given iterable has a length greater than 0:
    List test = new List();
    assertThat(test).isEmpty();
  • isNotEmpty(): Verifies whether the given iterable has a length of 0:
    List test = List.asList("Thor", "Hulk",
                            "Dr. Strange");
    assertThat(test).isNotEmpty ();
  • hasSize(): Verifies whether the length of the iterable is equal to the given value:
    List test = List.asList("Thor", "Hulk",
                            "Dr. Strange");
    assertThat(test).hasSize(3);

These are just some of the assertions available under AbstractIterableAssert. For the complete documentation, you can go to the link provided here: https://joel-costigliola.github.io/assertj/core-8/api/org/assertj/core/api/AbstractIterableAssert.html.

File assertions

File assertions are used to verify whether a file exists, can be written, or is readable, and also verify its contents. The following are some of the file assertion methods:

  • exists(): Proves that the file or directory exists:
    File file = File.createTempFile("test", "txt");
    assertThat(tmpFile).exists();
  • isFile(): Verifies whether the given object is a file (providing a directory will result in a failed test):
    File file = File.createTempFile("test", "txt");
    assertThat(tmpFile).isFile();
  • canRead(): Verifies whether the given file is readable by the application:
    File file = File.createTempFile("test", "txt");
    assertThat(tmpFile).canRead();
  • canWrite(): Verifies whether the given file is modifiable by the application:
    File file = File.createTempFile("test", "txt");
    assertThat(tmpFile).canWrite();

These are just some of the assertions available under AbstractFileAssert. For the complete documentation, you can go to the link provided here: https://joel-costigliola.github.io/assertj/core-8/api/org/assertj/core/api/AbstractFileAssert.html.

Map assertions

Map assertions are used to check a map based on its entries, keys, and size. The following are some of the map assertion methods:

  • contains(): Verifies whether the map contains the given entries:
    Map<name, Hero> heroes = new HashMap<>();
    Heroes.put(stark, iron_man);
    Heroes.put(rogers, captain_america);
    Heroes.put(parker, spider_man);
    assertThat(heroes).contains(entry(stark, iron_man),
      entry(rogers, captain_america));
  • containsAnyOf(): Verifies whether the map contains at least one of the entries:
    Map<name, Hero> heroes = new HashMap<>();
    Heroes.put(stark, iron_man);
    Heroes.put(rogers, captain_america);
    Heroes.put(parker, spider_man);
    assertThat(heroes).contains(entry(stark, iron_man), entry(odinson, thor));
  • hasSize(): Verifies that the size of the map is equal to the given value:
    Map<name, Hero> heroes = new HashMap<>();
    Heroes.put(stark, iron_man);
    Heroes.put(rogers, captain_america);
    Heroes.put(parker, spider_man);
    assertThat(heroes).hasSize(3);
  • isEmpty(): Verifies that the given map is empty:
    Map<name, Hero> heroes = new HashMap<>();
    assertThat(heroes).isEmpty();
  • isNotEmpty(): Verifies that the given map is not empty:
    Map<name, Hero> heroes = new HashMap<>();
    Heroes.put(stark, iron_man);
    Heroes.put(rogers, captain_america);
    Heroes.put(parker, spider_man);
    assertThat(heroes).isNotEmpty();

These are just some of the assertions available under AbstractMapAssert. For the complete documentation, you can go to the link provided here: https://joel-costigliola.github.io/assertj/core-8/api/org/assertj/core/api/AbstractMapAssert.html.

We have learned about the different assertion methods using AssertJ; now, we will implement and write our unit test in our Spring Boot application.

Writing a test

In this section, we will now start writing our unit tests in our Spring Boot application. As we go back to our application, the services and repository are the essential parts of our application where we need to implement unit tests, as the services contain the business logic and can be modified often, especially when new features are added. The repository includes methods for CRUD and other operations.

We will be implementing two approaches in writing our unit tests. The first method is using an in-memory database such as H2 to store our created data when running unit tests. The second method is mocking our objects and repository using the Mockito framework.

Testing with the H2 database

The first approach that we will implement in writing our tests is using JUnit and AssertJ with the H2 database. The H2 database is an in-memory database that allows us to store data in the system memory. Once the application is closed, it will delete all the stored data. H2 is usually used for Proof-of-Concept or unit testing.

We have already added an H2 database in Chapter 4, Setting Up the Database and Spring Data JPA, but if you have missed this part, in order for us to add the H2 dependency, we will add the following into our pom.xml file:

<dependency>
<groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope>
</dependency>

After successfully adding the dependency, we will add our h2 configuration under our test/java folder. We will add a new resource bundle and create a new application to accomplish this. A properties file will be used for the unit tests and we will place the following configuration:

spring.datasource.url=jdbc:h2://mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.username={username}
spring.datasource.password={password}
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true

In the preceding example configuration, first, we have specified that we want to store our data in a test.mv.db file using the spring.datasource.url property. We can also override the username and password for our H2 console using the spring.datasource.username and spring.datasource.password properties, and we have also specified that the tables will be created once the application starts and will be dropped when the application stops.

Testing a service

Now, we will create a package under our test/java folder. This is where we will write our tests. We will create a similar package from our main folder. In this case, we will make com.example.springbootsuperheroes.superheroes.antiHero.h2.service. Under the newly created package, we will create a new class named AntiHeroH2ServiceTest, where we will start writing our tests for AntiHeroService.

The first step we need to take is to annotate our class using the @DataJpaTest annotation. The annotation allows the service to focus only on the JPA components by disabling the full auto-configuration and just applying the configuration related to the tests. The next step is to add the dependency of our AntiHeroService, which is AntiHeroRepository. We will declare a new AntiHeroRepository and use the @Autowired annotation to inject the dependency, and we will also declare AntiHeroService, as this is the service that we need to test. We will have the following code:

@DataJpaTest
public class AntiHeroH2ServiceTest {
    @Autowired
    private AntiHeroRepository repo;
    private AntiHeroService service;
}

After injecting our dependency and annotating our class, the next thing we would want to consider is what the possible properties we want to have before running each of the tests are; in this case, we would like to have an instance of AntiHeroService created before running a test case. To accomplish this, we will make a method annotated with the @BeforeEach annotation, and create a new instance of AntiHeroService with AutoWired AntiHeroRepository as the parameter:

@BeforeEach
public void setup() {
    service = new AntiHeroService(repo);
}

Now, we can write a test case for our service; our goal is to write a test for each method that AntiHeroService possesses.

Let’s have the list of the methods for AntiHeroService:

  • Iterable<AntiHeroEntity> findAllAntiHeroes: Should return the list of anti-heroes.
  • AntiHeroEntity addAntiHero(AntiHeroEntity antiHero): Should add a new anti-hero entity.
  • void updateAntiHero(UUID id, AntiHeroEntity antiHero): Should update the anti-hero based on the given ID.
  • AntiHeroEntity findAntiHeroById(UUID id): Should return the anti-hero with the given ID; if it is not found, it will return NotFoundException.
  • void removeAntiHeroById(UUID id): Should remove the anti-hero in the database based on the given ID.

Let’s first write a test for the findAllAntiHeroes() method. The possible test case for the method is to check whether the method retrieves all the anti-heroes successfully in the database. To test this scenario, we would want to add a single entity or a list of test anti-hero entities to our H2 database first. We can call the findAllAntiHeroes() method to retrieve the newly added entities in the database. Let’s see the example unit test here:

@Test
public void shouldFindAllAntiHero() {
    AntiHeroEntity antiHero = new AntiHeroEntity();
    antiHero.setFirstName("Eddie");
    antiHero.setLastName("Brock");
    antiHero.setHouse("MCU");
    service.addAntiHero(antiHero);
    Iterable<AntiHeroEntity> antiHeroList =
      service.findAllAntiHeroes();
    AntiHeroEntity savedAntiHero =
      antiHeroList.iterator().next();
    assertThat(savedAntiHero).isNotNull();
}

In the preceding code example, we can see that we have created a new anti-hero instance to be an exemplary piece of data in the database memory first. We have added the data to our database using the addAntiHero() method. After successfully inserting the data, we can check or assert whether we can retrieve the newly created anti-hero using the findAllAntiHeroes() method. In the scenario here, we have retrieved the first data in our anti-hero list. We used assertThat(savedAntiHero).isNotNull() to validate that the first element of the list is not null.

Now, let’s write a test for the addAntiHero() method. The test that we will create for the following method is mostly similar to the test that we have created for the findAllAntiHeroes() method. The possible test case for the following method is to check whether the entity is being added to our database successfully.

Let’s have a look at the following example unit test:

@Test
public void shouldAddAntiHero() {
    AntiHeroEntity antiHero = new AntiHeroEntity();
    antiHero.setFirstName("Eddie");
    antiHero.setLastName("Brock");
    antiHero.setHouse("MCU");
    service.addAntiHero(antiHero);
    Iterable<AntiHeroEntity> antiHeroList =
      service.findAllAntiHeroes();
    AntiHeroEntity savedAntiHero =
      antiHeroList.iterator().next();
    assertThat(antiHero).isEqualTo(savedAntiHero);
}

We created a new anti-hero entity in the preceding code example and inserted it into our database using the addAntiHero() method. After adding the latest data, we can retrieve the list and validate whether our new data is in the database. In the given scenario, we retrieved the first piece of data in our anti-hero list, and we used assertThat(antiHero).isEqualTo(savedAntiHero); to check whether the data we retrieved was equal to the data we instantiated.

Next, let’s now write the test for updateAntiHeroMethod();. The possible test case for the following method is to check whether the method successfully modifies a piece of information for a specific entity in our database.

Let’s have a look at the example unit test that satisfies the test case here:

@Test
public void shouldUpdateAntiHero() {
    AntiHeroEntity antiHero = new AntiHeroEntity();
    antiHero.setFirstName("Eddie");
    antiHero.setLastName("Brock");
    antiHero.setHouse("MCU");
    AntiHeroEntity savedAntiHero  =
      service.addAntiHero(antiHero);
    savedAntiHero.setHouse("San Francisco");
    service.updateAntiHero(savedAntiHero.getId(),
                           savedAntiHero);
    AntiHeroEntity foundAntiHero =
      service.findAntiHeroById(savedAntiHero.getId());
    assertThat(foundAntiHero.getHouse()).isEqualTo(
      "San Francisco");
}

We created a new anti-hero entity in the preceding code example and inserted it into our database using the addAntiHero() method. After adding the entity, we updated the added anti-hero’s house information to "San Francisco" and saved it in our database using updateAntiHeroMethod(). Lastly, we have retrieved the modified anti-hero using its ID and validated that the house information was modified by adding the assertThat(foundAntiHero.getHouse()).isEqualTo("San Francisco"); assertion.

Next, we would now create a unit test for the removeAntiHeroById() method. The possible test case for the method is to validate whether an entity with a corresponding ID has successfully been deleted from the database.

Let’s have a look at the example unit test that satisfies the test case:

@Test
public void shouldDeleteAntiHero() {
    assertThrows(NotFoundException.class, new Executable() {
        @Override
        public void execute() throws Throwable {
            AntiHeroEntity savedAntiHero  =
              service.addAntiHero(antiHero);
            service.removeAntiHeroById(
              savedAntiHero.getId());
            AntiHeroEntity foundAntiHero =
              service.findAntiHeroById(
                savedAntiHero.getId());
            assertThat(foundAntiHero).isNull();
        }
    });
}

In the preceding example, we can see that we have added some additional elements in writing our unit test; we have created a new instance of Executable(), where we have placed our main code. We have asserted our Executable() with NotFoundException.class. The main reason for this is that we expect that findAntiHeroByID() will return the NotFoundException error, as we have deleted the entity in our database.

Remember that when asserting errors, we should use assertThrows().

We have successfully written a test for our services and now, we will implement unit tests at the repository level.

Testing a repository

Writing a test for the repository of our application is mostly the same as how we write our tests at the service level; we also treat them as services and we test them if there are additional methods added to the repository.

The example that we will take is writing a unit test for our UserRepository. Let’s have a recap of the methods that UserRepository possesses:

  • Boolean selectExistsEmail(String email): Returns true when the user exists with the given email
  • UserEntity findByEmail(String email): Returns the user when the given email exists in the database

To start writing our test, first, we will create a new package named user.repository under the com.example.springbootsuperheroes.superheroes package, and we will make a new class called UserRepositoryTest. After successfully creating the repository, we will annotate the class with @DataJPATest so that it focuses only on the JPA components and inject AntiHeroRepostiory using the @Autowired annotation.

Our class will now look as follows:

@DataJpaTest
class UserRepositoryTest {
    @Autowired
    private UserRepository underTest;
}

Now, we can write our tests after successfully injecting the repository. First, we want to write a test for the selectExistsEmail() method. The possible test case for the method is that it should return true if the email exists in our database.

Let’s have a look at the following example code:

@Test
void itShouldCheckWhenUserEmailExists() {
    // give
    String email = "[email protected]";
    UserEntity user = new UserEntity(email, "21398732478");
    underTest.save(user);
    // when
    boolean expected = underTest.selectExistsEmail(email);
    // then
    assertThat(expected).isTrue();
}

We have added an example user entity into our database in the example unit test. The selectExistsEmail() method is expected to return true. This should retrieve the added user with the given email.

The next test is for the findByEmail() method; this is almost similar to the test we have created for the selectExistsEmail() method. The only thing we need to modify is the assertion, as we are expecting a return value of the User type.

Let’s have a look at the following example code:

@Test
void itShouldFindUserWhenEmailExists() {
    // give
    String email = "[email protected]";
    UserEntity user = new UserEntity(email, "21398732478");
    underTest.save(user);
    // when
    UserEntity expected = underTest.findByEmail(email);
    // then
    assertThat(expected).isEqualTo(user);
}

We have successfully written a test for our services and repository with JUnit, AssertJ, and the H2 database. In the next section, we will use the second implementation on writing unit tests using JUnit and AssertJ with Mockito.

Writing tests in a service using Mockito

In the previous section, we created our unit tests using the H2 database; in this approach, we will completely omit the use of the database and utilize the concept of mocking in creating sample data in our unit tests. We will achieve this by using Mockito. Mockito is a mocking framework in Java that allows us to test classes in isolation; it does not require any databases.

It will enable us to return dummy data from a mocked object or service. Mockito is very useful, as this makes unit testing less complex, especially for larger applications, as we don’t want to test the services and dependencies simultaneously. The following are the other benefits of using Mockito:

  • Supports return values: Supports mocking return values.
  • Supports exceptions: Can handle exceptions in unit tests.
  • Supports annotation: Can create mocks using annotation.
  • Safe from refactoring: Renaming method names or changing the order of parameters will not affect the tests, as mocks are created at runtime.

Let’s explore the different features of Mockito for writing unit tests.

Adding behavior

Mockito contains the when() method where we can mock the object return value. This is one of the most valuable features of Mockito, as we can define a dummy return value of a service or a repository.

Let’s have a look at the following code example:

public class HeroTester {
   // injects the created Mock
   @InjectMocks
   HeroApp heroApp = new HeroApp();
   // Creates the mock
   @Mock
   HeroService heroService;
   @Test
   public void getHeroHouseTest(){
      when(heroService.getHouse())).thenReturn(
        "San Francisco ");
   assertThat(heroApp.getHouse()).isEqualTo(
     "San Francisco");
 }
}

In the preceding code example, we can see that we have mocked HeroService in our test. We have done this to isolate the class and not test the functionality of Heroservice itself; what we want to test is just the functionality of HeroApp. We have added behavior for the heroService.getHouse() method by specifying a mock return thenReturn() method. In this case, we expect that the getHouse() method will return a value of "San Francisco".

Verifying behavior

The next feature that we can use from Mockito is behavior verification in unit tests. This allows us to verify whether the mocked method is called and executed with parameters. This can be achieved using the verify() method.

Let’s take the same class example:

public class HeroTester {
   // injects the created Mock
   @InjectMocks
   HeroApp heroApp = new HeroApp();
   // Creates the mock
   @Mock
   HeroService heroService;
   @Test
   public void getHeroHouseTest(){
      when(heroService.getHouse())).thenReturn(
        "San Francisco ");
   assertThat(heroApp.getHouse()).isEqualTo(
     "San Francisco");
   verify(heroService).getHouse();
 }
}

In the preceding code example, we can see that we have added verify(heroService).getHouse() to our code. This validates whether we have called the getHouse() method. We can also validate whether the method is called with some given parameters.

Expecting calls

Expecting calls is an extended feature for behavior verification; we can also check the number of times that the mocked method has been called. We can do so by using the times(n) method. At the same time, we can also validate whether it has been called using the never() method.

Let’s have a look at the following example code:

public class HeroTester {
   // injects the created Mock
   @InjectMocks
   HeroApp heroApp = new HeroApp();
   // Creates the mock
   @Mock
   HeroService heroService;
   @Test
   public void getHeroHouseTest(){
     // gets the values of the house
     when(heroService.getHouse())).thenReturn(
       "San Francisco ");
    // gets the value of the name
    when(heroService.getName())).thenReturn("Stark");
   // called one time
   assertThat(heroApp.getHouse()).isEqualTo(
     "San Francisco");
   // called two times
   assertThat(heroApp.getName()).isEqualTo("Stark");
   assertThat(heroApp.getName()).isEqualTo("Stark");
   verify(heroService, never()).getPowers();
   verify(heroService, times(2)).getName();
 }
}

In the preceding code example, we can see that we have used the times(2) method to validate whether the getName() method from heroService has been called two times. We have also used the never() method, which checks that the getPowers() method has not been called.

Mockito, other than times() and never(), also provides additional methods to validate the expected call counts, and these methods are the following:

  • atLeast (int min): Validates whether the method is called at least n times
  • atLeastOnce (): Validates whether the method is called at least once
  • atMost (int max): Validates whether the method is called at most n times

Exception handling

Mockito also provides exception handling in unit tests; it allows us to throw exceptions on mocks to test errors in our application.

Let’s have a look at the following example code:

public class HeroTester {
   // injects the created Mock
   @InjectMocks
   HeroApp heroApp = new HeroApp();
   // Creates the mock
   @Mock
   HeroService heroService;
   @Test
   public void getHeroHouseTest(){
   doThrow(new RuntimeException("Add operation not
           implemented")).when(heroService.getHouse()))
   .thenReturn("San Francisco ")
  assertThat(heroApp.getHouse()).isEqualTo(
    "San Francisco");
 }
}

In the preceding example, we have configured heroService.getHouse(), once it is called, to throw RunTimeException. This will allows us to test and cover the error blocks in our application.

We have learned about the different features available in Mockito. Now, let’s proceed with writing our tests in our Spring Boot application.

Mockito in Spring Boot

In this section, we will now implement Mockito for writing unit tests in our Spring Boot application. We will be writing tests for our service again, and we will create another package under our test/java folder, which will be used for our unit tests using Mockito; we will make com.example.springbootsuperheroes.superheroes.antiHero.service. Under the newly created package, we will create a new class named AntiHeroServiceTest, where we will start writing our tests for AntiHeroService.

After successfully creating our class, we will need to annotate the class with @ExtendWith(MockitoExtension.class) to be able to use the Mockito methods and features. The next step is to mock our AntiHeroRepository and inject it into our AntiHeroRepositoryService. To accomplish this, we would use the @Mock annotation with the declared repository and the @InjectMocks annotation with the declared service, and our class would now look as follows:

@ExtendWith(MockitoExtension.class)
class AntiHeroServiceTest {
    @Mock
    private AntiHeroRepository antiHeroRepository;
    @InjectMocks
    private AntiHeroService underTest;
}

In the preceding example, we successfully mocked our repository and injected it into our service. We can now start mocking our repository’s return values and behavior in our tests.

Let’s have some example tests in our AntiHeroService; in an example scenario, we will write a test for the addAntiHero() method. The possible test case for this one is to verify whether the save() method from the repository is called and the anti-hero is successfully added.

Let’s have a look at the example code here:

@Test
void canAddAntiHero() {
    // given
    AntiHeroEntity antiHero = new AntiHeroEntity(
            UUID.randomUUID(),
            "Venom",
            "Lakandula",
            "Tondo",
            "Datu of Tondo",
            new SimpleDateFormat(
              "dd-MM-yyyy HH:mm:ss z").format(new Date())
    );
    // when
    underTest.addAntiHero(antiHero);
    // then
    ArgumentCaptor<AntiHeroEntity>
    antiHeroDtoArgumentCaptor =
      ArgumentCaptor.forClass(
            AntiHeroEntity.class
    );
    verify(antiHeroRepository).save(
      antiHeroDtoArgumentCaptor.capture());
    AntiHeroEntity capturedAntiHero =
      antiHeroDtoArgumentCaptor.getValue();
    assertThat(capturedAntiHero).isEqualTo(antiHero);
}

In the preceding example, the first step is always to create a sample entity that we can use as a parameter for adding a new anti-hero; after invoking the addAntiHero() method that we are testing, we have verified whether the save() method of AntiHeroRepository has been invoked using the verify() method.

We have also used ArgumentCaptor to capture the argument values we have used in the previous way, which will be used for further assertions. In this case, we have asserted that the captured anti-hero is equal to the anti-hero instance we have created.

Summary

With this, we have reached the end of this chapter. Let’s have a recap of the valuable things you have learned; you have learned about the concepts of JUnit, which is a testing framework that offers features such as fixtures, test suites, and classes to test the methods in our application. You have also learned about the application of AssertJ with JUnit, which provides a more flexible way of asserting objects in our unit tests; and lastly, you have also learned about the importance of Mockito, which provides us with the ability to mock objects and services.

In the next chapter, we will now develop our frontend application using Angular. We will discuss how to organize our features and modules, structure our components inside our Angular file structure, and add Angular Material to the user interface.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.78.30