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:
The link to the finished version of this chapter is here: https://github.com/PacktPublishing/Spring-Boot-and-Angular/tree/main/Chapter-09.
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:
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 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:
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.
//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.
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());
}
}
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:
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:
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.
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:
Assert.assertTrue(condition)
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 are used to check whether conditions return true or false. The assertion methods are as follows:
Assertions.assertThat(4 > 3).isTrue()
Assertions.assertThat(11 > 100).isFalse()
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:
Assertions.assertThat('a').isLowerCase();
Assertions.assertThat('a').isUpperCase();
Assertions.assertThat('a').isEqualTo('a');
Assertions.assertThat('a').isEqualTo('b');
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 are used to check the fields, types, access modifiers, and annotations in a specific class. The following are some of the class assertion methods:
Interface Hero {}
class Thor implements Hero {}
Assertions.assertThat(Thor.class).isNotInterface()
Interface Hero {}
class Thor implements Hero {}
Assertions.assertThat(Hero.class).isInterface()
public class Hero {}
protected class AntiHero {}
Assertions.assertThat(Hero.class).isPublic()
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 are used to verify an iterable or array object based on its length and contents. The following are some of the iterable assertion methods:
List test = List.asList("Thor", "Hulk",
"Dr. Strange");
assertThat(test).contains("Thor");
List test = new List();
assertThat(test).isEmpty();
List test = List.asList("Thor", "Hulk",
"Dr. Strange");
assertThat(test).isNotEmpty ();
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 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:
File file = File.createTempFile("test", "txt");
assertThat(tmpFile).exists();
File file = File.createTempFile("test", "txt");
assertThat(tmpFile).isFile();
File file = File.createTempFile("test", "txt");
assertThat(tmpFile).canRead();
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 are used to check a map based on its entries, keys, and size. The following are some of the map assertion methods:
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));
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));
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);
Map<name, Hero> heroes = new HashMap<>();
assertThat(heroes).isEmpty();
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.
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.
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.
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:
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.
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:
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.
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:
Let’s explore the different features of Mockito for writing unit tests.
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".
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 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:
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.
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.
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.