[JAVA] Write test code in Spring Boot

Overview

I wrote the test code of MVC related processing and login related processing in Spring Boot through trial and error, so I will summarize it as a memorandum.

If you know how to write better, I would be grateful if you could kindly teach me (╹◡╹)

The source code is available on GitHub.

Preface that can be managed without reading (click to open and close) Nowadays, I've often heard that it's better to write test code. I wondered if I should write it, but when I looked it up, it stopped at the test of the four arithmetic operations level, so how should I write it in the actual application ...!? I wasn't ready to write the test code.
I thought it would be awkward as it is, so I sacrificed the Obon holiday and wrote the test code through trial and error while suffering from errors in Spring Boot. There are many points that I haven't been able to understand yet, but for the time being, I've established something like a "writing pattern" to some extent, so I'd like to summarize it as a memorandum.
There aren't many articles about Spring Boot test code that are systematically organized in both Japanese and English, so there are a lot of groping parts, but I hope it helps you understand even a little ('ω').

Target readers / goals

In this article, we will focus on how to write test code in Spring Boot, so if you meet the following, it will be painful.

With the above assumptions, reading this article will (probably) enable you to:

In addition, this time, the screen display and the test of the JS part etc. are excluded. In the future, I'll cover that area as well, but suddenly trying to test everything would be too complicated, so I'll start small. Step up is important.

Supplement: How far will the test be done automatically (click to open and close) The importance of writing test code is often mentioned, but replacing manual testing with automated testing requires a lot of experience and cost.
In the case of business applications, there are various things and things that make it difficult to incorporate automated testing. However, even if there are no restrictions in personal development, suddenly everything is automated !! When I rush, my heart is almost broken (I broke ƪ (˘⌣˘) ʃ).
Therefore, I think it is a painful way to focus on the part of "what can be done by automatic testing and help" and gradually increase the part that can be automatically tested as much as possible. Writing test code requires a thorough understanding of the framework and language, so I think it's best to stay with them for a long time as your technical skills grow.

Now let's get back to the scope of this test code. When I'm writing a lot of apps with Spring Boot, I've (personally) experienced a lot of bugs in the following parts.
-The expected value is not passed to the Model passed to View. -When I look at the execution result of Dao layer processing in DB, it is not as expected. -Processing according to user authority is not working as expected
The above overlaps nicely with the coverage of this time. First of all, I would like to aim to increase the enjoyment of development by making it possible to efficiently debug the part that has a lot of bugs and is having a hard time.

A simple diagram of the above coverage is as follows.

image.png

I will touch on the area from when the request is thrown to when View is requested to display it. Therefore, View is basically good night this time.

environment

For details, please see pom.xml on GitHub.

pom.xml(Excerpt)


        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
		<dependency>
		    <groupId>org.junit.jupiter</groupId>
		    <artifactId>junit-jupiter-api</artifactId>
		    <scope>test</scope>
		</dependency>
		<dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.dbunit/dbunit -->
		<dependency>
		    <groupId>org.dbunit</groupId>
		    <artifactId>dbunit</artifactId>
		    <version>2.5.1</version>
		    <scope>test</scope>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.github.springtestdbunit/spring-test-dbunit -->
		<dependency>
		    <groupId>com.github.springtestdbunit</groupId>
		    <artifactId>spring-test-dbunit</artifactId>
		    <version>1.3.0</version>
		    <scope>test</scope>
		</dependency>

Test range

Now, let's see how to actually write test code from here. However, Spring Boot test code is written by combining some knowledge, so if you try to cover everything at once, the difficulty level will jump up.

Therefore, I would like to divide it into the following four steps.

The coverage of Levels 1 to 3 covered in this article is roughly illustrated as follows.

image.png

Let's start with Hello World.


Level1. Test Hello World

This is the familiar Hello World. Let's take a look at some common processes, such as when a request is thrown to "/ hello / init" below, "hello" is returned as the view name.

HelloController.java


@Controller
@RequestMapping("/hello")
public class HelloController {
	
	@RequestMapping("/init")
	private String init() {
		return "hello";
	}
}

It's simple to do, but it requires some new knowledge to write test code, so it's a very important part of getting started with Spring Boot test code.

First of all, the code itself is not very long, so to get the big picture, write the actual test code below.

HelloControllerTest.java


@AutoConfigureMockMvc
@SpringBootTest(classes = DbMvcTestApplication.class)
public class HelloControllerTest {
	
	//mockMvc Mock object for handling Http request and response without deploying to Tomcat server
	@Autowired
	private MockMvc mockMvc;
	
	//Specify view in get request and judge success / failure of request by http status
	@Test
void init processing runs and returns 200() throws Exception {
		// andDo(print())Show request response with
		this.mockMvc.perform(get("/hello/init")).andDo(print())
			.andExpect(status().isOk());
	}

Suddenly, when I was writing an app, I found a lot of things that I wasn't familiar with. The processing of this test code is roughly divided into "two" blocks, so let's take a closer look at each.

Class annotation

The annotations given to the class have important information to get an overall picture of the test code. I don't usually use it, but if you get a good overview, the distance to the test code will be shortened.

AutoConfigureMockMvc annotation

It is an annotation to use something called MockMvc. So who is MockMvc?

This is to separate the physical "server" from the created "web application". It doesn't come out very well, so let's look at what makes us happy by disconnecting it.

Supplement: Need to use mock (click to open and close) One of the benefits of using a mock is that it reduces the execution time of test code, but limiting the scope of influence is also an important factor.
For example, when testing the processing of the service layer that accesses the Dao layer, if it is a simple process, I think that the test can be realized without much effort without using a mock. However, if the code becomes complicated and the creators are different, when a bug occurs, it is necessary to investigate whether the cause is in the "service layer" or the "Dao layer". It will be. Here, if you mock the Dao layer "appropriately" and complete the service layer test first, if a bug occurs when you replace it with the real class, you can limit the cause to the Dao layer. It will be possible.
As a matter of fact, even if you don't use mock in personal development, you have to implement everything from 1 to 100 by yourself, so you may not have many opportunities to use mock for purposes other than reducing "execution time". However, the idea of limiting the range of influence, not limited to test code, becomes important, and in team development, you do not make everything by yourself, so the idea and handling of mock is also at the overview level. I think you should know it.
However, I can't say that I'm completely good at using the mock, so if you know a better way of thinking, I'd be very happy if you could tell me. The following is a reference material. [What is a Mock object](https://www.itmedia.co.jp/im/articles/1111/07/news180.html) [Advantages of Mock](https://stackoverflow.com/questions/22783772/what-is-the-benefits-of-mocking-the-dependencies-in-unit-testing)

For personal development, mock the server part to reduce the execution time of the test code, and for team development, mock the part that you are not involved in and write the test code after limiting the range of influence. I think it's better to proceed.

The following is a reference material.

Japanese material of MockMvc Official Web Layer Test


SpringBootTest annotation

This is an annotation that seems to be very important. It's actually a very important annotation. In the spring boot unit test, annotations that almost always appear and have many functions, so I would like to take a step-by-step look at the functions.

The following two functions are important here.

First, let's talk about ʻExtendWith annotation``. The RunWith annotation was often used in the explanation of the Spring Boot test, but the RunWith annotation is an annotation for Junit4, and the ExtendWith annotation is an annotation for Junit5.

It is used for general-purpose implementation of test pre-processing and post-processing. Then, SpringExtension is passed to the "Extension" class where the implementation of general-purpose processing, which is the value property, is written. Since it is an advanced story, I will omit it this time, but this Extension class plays an important part such as instantiation of ApplicationContext which has the role of DI container. See Official for details.

Well, as I wrote for a long time, using a DI container in Spring Boot is no longer a matter of course, and writing the ExtendWith annotation every time is troublesome. As you can see from the Documentation, it is natural to use it. If it is, it is okay to include it, so only the SpringBootTest annotation is OK.

By creating annotations that include multiple functions in this way, it is possible to simplify the description of the premise of the test code, but this time we will emphasize clarity, so for integration Annotations are omitted.


Next, let's take a look at the ApplicationContext settings. ApplicationContext is often used as a word, but I think it's okay if you think of it as a "DI container".

It's not possible to solve everything by simply passing the main class, but I'd like to touch on that in the area of testing at the Dao layer, which does not require HTTP request / response.

Supplement: What to pass to the classes property (click to open / close) As you can see in the Documentation (https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html#classes--), the "classes" property The class set in is the one that has the role of "Configuration" and is used to realize the DI container. In the previous explanation, the one with the SpringBootApplication annotation realized the "setting" for the DI container.
However, this time HelloController does not depend on other classes in the first place (as far as you can see), so even if you pass "HelloController" to the "classes" property, it will work. However, since it is assumed that the "classes" property is originally passed a class that has a role as a setting class, a class with the SpringBootApplication annotation or a config class that defines the settings required for testing It would be better to pass it. When using any function, if you at least read what the parameters described in the document are supposed to be, you will be addicted to unexpected behavior and you will be at a loss. I think that will be less. (Self-discipline)
* In fact, if you define only HelloController as WebApplicationContext, the Body part of MockMvc's response will be empty. (The contents have not been seen, but it seems that the cause is that ViewResolver and View are not instantiated.)

The explanation has become long just by setting, but once you understand it, it is an important part that can be used when writing other test code, so it may be good to sit down and study. .. (I understood that I didn't understand DI at all, so it was a good opportunity to review Spring)

Test code

I finally got to the actual test code. It's simpler and more fun than the detailed settings so far. Now that the intervals are open, let's take a look at the part related to the test code again.

HelloControllerTest.java(Excerpt)


//Specify view in get request and judge success / failure of request by http status
    @Test
void init processing runs and returns 200() throws Exception {
        // andDo(print())Show request response with
        this.mockMvc.perform(get("/hello/init")).andDo(print())
            .andExpect(status().isOk());
    }

The above test code performs two processes, "execution of request" and "verification of response".

Both processes are performed based on the instance of "MockMvc", and are basically described in one statement as described above. By putting them together in one sentence, it is possible to unravel what the test is doing in the form of an English sentence. Let's see how it is actually written.

To summarize the above, the English text will be as follows. (I'm not very good at writing, so please feel it in the atmosphere ƪ (˘⌣˘) ʃ)

Perform get request to [/hello/init] and print the result, and I expect that status is 200 OK.

Since the programming language is written in English, there is of course the advantage that native English speakers can read and write test code in a structure similar to the English sentences they are accustomed to. (There seems to be no way to escape from English, so it may not be painful if you at least bite into reading and listening.)

It's a little off topic, but by describing the test method and expected results in an easy-to-read format, important information for understanding the system specifications and the actual source code can be easily obtained from the test code. Become. On the flip side, test code that doesn't meet the spec will miss bugs and won't help you understand the spec, so it's bad code.

From such a simple code stage, it may be good to get into the habit of writing test code while always being aware of what should be realized as a function in the implemented process.

After a lot of digressions, the request response when Hello World is verified with the test code is as follows. image.png

image.png

You should be able to confirm that the contents described above are satisfied.

Finally, I can confirm that Hello World is working properly. I did it.


Test the Model

Since there is a limit to what can be verified with the HelloWorld test code alone, I would like to take a look at Model verification as another practical thing at Level 1.

Model here refers to "Java object referenced by View" and is often written in Model.addAttribute etc. It's very common that when I thought that I packed the value in the Model nicely, it wasn't actually entered, so I didn't have to start the server and access the screen with a click ... It would be very useful if we could verify that it was working.

Below, we will see how to actually verify the contents of the Model with test code. The content is easier to understand than the ones I've touched on so far, so I hope you'll master how to use it.


Code to be verified

First, let's look at the code to be tested. That said, it's just a slight extension of HelloController, and it's not that difficult throughout, so I'll put it all at once here.

HelloController.java


@Controller
@RequestMapping("/hello")
public class HelloController {
	
	@RequestMapping("/init")
	private String init(Model model) {
		
		//User list First, manually generate
		List<User> userList = new ArrayList<User>();

		User user = new User();
		user.setUserId(0L);
		user.setUserName("test0");

		User user2 = new User();
		user2.setUserId(1L);
		user2.setUserName("test1");
		
		userList.add(user);
		userList.add(user2);
		
		//Set a list of users on the form and add it to the model to lay the groundwork for verifying that it was successfully added to the model.
		DbForm form = new DbForm();
		form.setUserList(userList);
		
		model.addAttribute("message", "hello!");// 1
		
		model.addAttribute("user", user);// 2
		
		model.addAttribute("dbForm", form);// 3
		
		return "hello";
	}

}

In the following, we will follow each pattern for model.addAttribute.

① Was String stored in Model?

Let's start with a simple example.

In model.addAttribute ("message "," hello! ");, The model simply stores "message" as the key and "hello" as the value.

The test code to verify this is as follows.

HelloControllerTest.java(Excerpt)


@Test
hello is passed to the model message in void init processing() throws Exception {
		this.mockMvc.perform(get("/hello/init"))
			.andExpect(model().attribute("message", "hello!"));
	}

The test code is also very simple, and as you can see from the part ʻandExpect (model (). Attribute ("message", "hello!")) `, It is almost the same as stuffing into the actual Model. You can also write test code.

image.png

If you actually look at the result, you can see that the Model part is correctly packed with values.

If it is a simple object, the property is only one level, so it can be written simply. However, if you take a closer look at the results, you will find that the value value is written in a dubious string. It represents the instance of the object itself. Of course, there are times when you want to verify that an object isn't null, but most of the time you want to know if a particular property in the object has the expected value.

Below we'll look at validating a Model packed with these nested properties.

(2) Is the value of the userName property of the user Entity packed in the Model as expected?

Now, verifying nested objects makes the test code a bit more complicated, but it's much simpler than interpreting annotations, so let's explore each one.

Here, regarding the processing of model.addAttribute ("user ", user);, verify whether the property "userName" of the "user" instance is as expected (here, the value "test0"). To go.

Anyway, let's start by looking at the actual test code.

HelloControllerTest.java(Excerpt)


@Test
User Entity is stored in the model by void init processing() throws Exception {
		this.mockMvc.perform(get("/hello/init"))
			.andExpect(model()
					.attribute("user", hasProperty(
										"userName", is("test0")
										)
							)
					);
	}

Suddenly the structure changed drastically. This is not the Spring Boot test code, but rather the processing by the framework that handles the so-called "Matcher" that verifies the validity of the test called "Hamcrest".

The method hasProperty for validating the properties of an object is used in static import, so it has a structure of HasPropertyWithValue.hasProperty to be exact.

And this method has the following roles. (Quoted from the official)

Creates a matcher that matches when the examined object has a JavaBean property with the specified name whose value satisfies the specified matcher.

Somehow an esoteric English sentence came out, but I think that it will come nicely if you look at an actual example. assertThat(myBean, hasProperty("foo", equalTo("bar")) This means that the object "myBean" has a property called "foo" and the value of the foo property is "bar". That's exactly what we want to verify in this test. A simple example of this example would be ʻassertThat (user, hasProperty ("userName", is ("test0")); `. Since the return value of hasProperty belongs to the Matcher type, you can also write the property in a nested manner.

Verification of nested properties inevitably lengthens the code and makes it difficult to understand the correspondence between parentheses, so I think it is necessary to take some measures to make it easier to read, such as devising indentation as in the above example.

Anyway, this makes it possible to handle complicated models. As an example of the final Model, let's take a look at the test code for the List object.

Supplement: What is Matcher? Matcher has come out as a matter of course, but if you haven't touched JUnit, you may not be familiar with it, so I will briefly touch on it as a supplement. Matcher itself is an interface, designed to describe the validation process in a test (equal or not equal, all conditions ...) in a simple and easy-to-read format.
For example, the commonly used "assertThat" method is written in the form ʻassertThat (1 + 1, is (2));`. This is to write the test code in an easy-to-read English format, similar to the perform process above. And what you specify in the second argument of the assertThat method represents "Matcher". If you actually follow the contents of the assertThat method, ʻIf (! matcher.matches (actual)) {Processing when there is no match} ` Here, the matches method of the Matcher type instance specified in the second argument is called. Since this result is a boolean, I think it's a good idea to roughly interpret Mathcer as a boolean value for storing the success or failure of verification.
Even if it's a nested property, in the end it's just verifying that the specific property value at the bottom layer is equal to what you expected, so you may be able to understand it unexpectedly if you think simply and not be too prepared. ..

Reference

③ Does the list packed in Model hold the expected properties?

Let's look at the final Model pattern, which is nested and has a list structure. As a common example of model.addAttribute ("dbForm ", form);, verify that the "list of users in the Form object" is what you expect. As an example, first write the code below.

HelloControllerTest.java(Excerpt)



	//For list elements, access the list in any order with hasItem and verify whether there is an element whose specified property has the specified value.
	//Make test green only if present
	@Test
User list is stored in the model form by void init processing() throws Exception {
		
		this.mockMvc.perform(get("/hello/init"))
			.andExpect(model().attribute("dbForm", hasProperty(
					"userList", hasItem(
						hasProperty(
								"userName", is("test1")
						)
					)
			)));
	}

A new method called hasItem has appeared. Official It is said that it can be used only for list format objects. I am. And the method used here has Matcher as an argument.

In other words, roughly speaking, we are verifying that there is at least one that satisfies the Matcher passed as an argument in the list to be executed by the hasItem method. In this example, we want to verify that for each user element in the user list, there is at least one with the "userName" property set to "test1".

Since the list in the example has a small number of elements such as "2", it is possible to examine all the contents, but in the actual application, a list packed with hundreds or thousands of elements is often passed. .. It's hard to verify all this, though it can be coded. In such a case, it seems that reliability can be guaranteed to some extent if it is possible to verify whether the elements near the beginning, middle, and end meet the specifications. Therefore, instead of verifying all the elements of the list, it is only necessary to verify only a part as a representative element, so I think it is better to provide some test cases with the hasItem method.


By the way, it has become quite long because it was mixed with various supplementary explanations, but the level 1 test code is now verified. By completing Level 1, you can do the following:

Next, at Level 2, I would like to look at the verification of the database, which is the core of the application.

Level2. Test database operations

In the unit test with Spring Boot, we use something called "DbUnit" to verify the database. If you write it like this, even though Spring Boot alone is full, you will have to study more ... but I think that it is enough to learn how to use DbUnit easily. The important thing is to be aware of "what kind of work will be easier" by combining DbUnit and Spring Boot.

If you suddenly write about DbUnit in a mess, the image will be difficult to understand, so let's first follow the flow of how database operations are replaced from manual tests to automated tests.

Manual test

First, consider manually testing your database operations. I think that the test will proceed in the following flow.

The pros and cons of manual testing are left here, but here we will focus on "test reproducibility". If you are developing as a team, the result of SELECT will change from moment to moment, and UPDATE and DELETE processing will require some advance preparation if you want to meet the same conditions. If the tests executed are not reproducible, it will be difficult to identify whether or not degradation has actually occurred when repeated tests such as refactoring and regression tests are performed.

Now let's devise a little more about manual testing.

Manual construction of test state

In order to ensure reproducibility, I tried to incorporate the following process into the above manual test.

This ensured the reproducibility of the test. If this is the case, you can test with confidence ... !! It may not be possible if it is a small application, but if you back up the entire database, delete the entire database, restore the entire database, etc. every time you do a little verification, it will be a test. It will take a huge amount of time.

Even if you create a reproducible test state, if it takes a long time to execute the test itself or return the result, the test will not be executed at the right time. I just wanted to debug a bit, but if I wasn't good enough, I would have to wait for tens of minutes before the results came back, and if I wasn't good enough, I would end up putting it off and returning to a manual tick test.


So far, testing database operations while ensuring reproducibility may seem like a bit of a hurdle. However, by combining Spring Boot and DbUnit, although some preparation is required, the above tests can be executed with the touch of a button.

Now, let's take a look at the test code using Spring Boot and DbUnit as the main subject of Level 2.

Code to be verified

First, let's look at the process of getting the result with the royal road SELECT. The code of the Dao layer to be verified is described below.

UserDao.java(Excerpt)


     /**
	 *Get all user records from DB
	 *This time, for testing, the process was simplified.
	 * @return List of user entities
	 */
	public List<User> findAllUser() {
		
		QueryBuilder query = new QueryBuilder();
		
		query.append("select user_id, user_name from tm_user");
		
		return findResultList(query.createQuery(User.class, getEm()));
	}

Various processes are written, but the following two points should be noted.

The process of SELECTing records from the database will continue to recur, so the first step is to verify number of records in the database = list size.

I will use DbUnit immediately for verification, but some preparation is required before writing the actual test code. There are a lot of things to do in preparation, but once you master it, you can make it a routine for subsequent database operation tests, so I'd like to take a closer look.

I want to manage data with CSV

First, we'll lay the groundwork for managing database records in files. As a standard function of DbUnit, record transaction settings etc. are described in XML file, but this time we will manage records in CSV. There are various reasons, but in summary, the big thing is that you can write simply.

I'll take a few steps below, but all of them are simple, so I think you can understand them intuitively to some extent without going too far into DbUnit itself.

So, first of all, we will create a class to use the CSV file in the test.

CsvDataSetLoader

CsvDataSetLoader.class


public class CsvDataSetLoader extends AbstractDataSetLoader{
	
	@Override
	protected IDataSet createDataSet(Resource resource) throws Exception {
		return new CsvURLDataSet(resource.getURL());
	}
}

The following is a supplementary explanation of the important elements.

Literally an abstract class for reading some dataset. The dataset here stands for "a set of tables". Since this abstract class is an implementation class of the DataSetLoader interface, the class to be created will be of type "DataSetLoader". In other words, if you look at the class created at the class level, it is as simple as describing the information "This is a class for reading a dataset".

Again, as the name implies, it's a factory method for creating datasets. The Resource type "resouce" object passed as an argument has information and behavior for accessing the "real file". In the actual test, the resource object is in the form of storing the path of the CSV file to be processed.

In Official, This class constructs an IDataSet given a base URL containing CSV files As you can see, by getting the actual CSV file based on the above resource object and converting it to a dataset object, DbUnit can process it.

Some processing was written, but as the class name implies, this class is for reading the actual CSV file and making it available for testing database operations.

Once written, it can be used when testing database operations using CSV files in other apps, so here, if you can get an overview of what each process represents, the problem is. I think there isn't.


Now that the class for reading CSV has been completed, let's create a CSV file to be actually read. For a sample file, go to GitHub.

There are various ways to create the CSV file itself, but personally, it is recommended to use DBeaver to extract the CSV from the record and use it as it is. The following points should be noted when creating a CSV file.

You also need to be careful where you put the CSV files. (I put it in a strange place and got hooked.) Basically, it will be placed under src / test / resources. Reference

The specific file / folder structure is as follows.

image.png

You can see that there is a CSV file that looks like a table name under src / test / resources / testData. And next to it is an unfamiliar text file called table-ordering.txt. This is to prevent foreign key constraints and specifies the order in which the database tables are read. The specific writing method is as follows.

table-ordering.txt


TableA
TableB
TableC

Test code

Now that we're finally ready, we can get into the test code. The number of annotations will suddenly increase, but if you overcome this, the range in which you can write tests will expand dramatically, so I will do my best.

DBSelectTest.java


@DbUnitConfiguration(dataSetLoader = CsvDataSetLoader.class)
@TestExecutionListeners({
	  DependencyInjectionTestExecutionListener.class,
	  TransactionalTestExecutionListener.class,
	  DbUnitTestExecutionListener.class
	})
@SpringBootTest(classes = {DaoTestApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class DBSelectTest {
	
	@Autowired
	private UserDao userDao;
	
	//By setting the CSV file path in the value of DatabaseSetup, "table"-ordering.See txt "
	//Create a test table group by creating a sequential table
	//At this time, the path of value is "src"/test/Starting from under "resources", the file name of the CSV file is
	//Correspond to the name of the table
	//
	//Also,@By adding the Transactional annotation, you can roll back the transaction when the test is finished.
	//Can be tested without polluting the environment
	@Test
	@DatabaseSetup(value = "/testData/")
	@Transactional
	public void contextLoads() throws Exception {
		List<User> userList = userDao.findAllUser();
		
		//Did Dao successfully get records from the table?
		assertThat(userList.size(), is(2));
	}

}

Class annotation

In order to get the whole picture, we will start with the annotations given to the class.

DbUnitConfiguration

As you read, it is an annotation for setting various settings of DbUnit. By specifying the CsvDataSetLoader created above in the" dataSetLoader "property, you can read the CSV file. There seem to be various other settings, but at this stage I think there is no problem with the recognition that it is used to load CSV.

TestExecutionListeners

This is the most difficult part of this test code. I think this annotation should be enough to give you an overview and sort out what should be passed to the property. As an overview, it is for loading the necessary one of the TestExecutionListener, which defines the processing to be performed before and after the execution of the test code.

The description of each listener is roughly summarized in Official. , Here, I will briefly describe the ones that are often used.

Specify when using DI in the test code. By specifying, it is possible to inject the test target class from the DI container with Autowired etc.

Specify when setting the transaction for DB operation. After operating the DB, it is basic to return the DB to its original state, so it is basically essential for tests dealing with the DB.

Specify when setting the state of the database before and after the test with the annotation described later. As the name suggests, when using DbUnit, basically add it.

Supplement: DirtiesContext and listener default settings I didn't introduce it here, but you will often see a listener called `DirtiesContextTestExecutionListener`. It has the role of a handler for the DirtiesContext. DirtiesContext is for invalidating the cache of the context used to execute the test code.
If you enable DirtiesContext, the test code will take a long time to execute because the context is not cached on a test-by-test basis. It seems to be used when the test code changes the context and affects subsequent tests, but it seems unlikely to be used at the basic level, so you do not have to worry about it. Of course, if you choose not to use it, you need to clarify "why not use it", so I think it's better to keep the concept down. [Reference](https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-ctx-management-caching)
Next, let's change the story and add a supplement to the listener's default settings. At first glance at the formula, I thought that some were enabled by default and I should just pass in what I wanted to add. However, it is not actually enabled, and when I read the official wondering what this is ..., it seems that the default setting is valid only when it is not specified in the `TestExecutionListeners annotation`. The example here is specified to enable the listener for DbUnit, but in this case even if it is written in the default setting, it will not work unless you pass what you need. .. [Reference](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/TestContextBootstrapper.html#getTestExecutionListeners--)

SpringBootTest

Well, this is the second appearance. Here, a new webEnvironment property is passed. "MOCK" is set as the default value, and it seems that you are creating something called mock servlet environment. The details were not officially written much, but as far as the console output is seen, it seems to represent the process of generating the DispatcherServlet for testing used by MockMvc.

Since it is not necessary to exchange request / response with the server in the Dao layer alone, set "NONE" as the property value. This cuts the process of creating the context for MockMvc and makes the test finish a little faster.

And although the order has been reversed, what is passed to the classes property has also changed. That doesn't mean that the process has changed significantly, it just narrows the scope of component scans. If you look at the actual code, it will come to you.

DaoTestApplication.java


@EntityScan("app.db.entity")
@SpringBootApplication(scanBasePackages = "app.db.dao")
public class DaoTestApplication {

	public static void main(String[] args) {
		SpringApplication.run(DaoTestApplication.class, args);
	}

}

The target to be read is limited to "Dao layer" and "Entity handled by Dao". As the size of the app grows, it takes longer to load the package, and it takes longer to load. Since the processing related to database operations is changed frequently and we want to run the test code as much as possible, we have minimized the reading range to shorten the time as much as possible.

With this kind of ingenuity, the test will be completed in about one turn of the neck after stretching. If you want more speed, you'll have to mess around with the Config class and mess around with EntityManager, but it's not too fatally slow to test, so don't go that far.

The above settings can be done quickly, and the effect can be obtained as it is, so I think that this is enough at the basic stage.


Method annotation

It's about annotations again, but it's simpler than class-level annotations, so I think it'll come to your mind.

DatabaseSetup

This is an annotation to define the "initial state" of the database. If you specify the directory where the CSV file is located in the value property, the value will be packed in the database table based on the CSV file. You can also create different states by switching directories.

This allows you to always reproduce the state of the table when you want to start the test, without having to manually insert it into the table. Thank you.

Transactional

It is a familiar one that is often used in actual application development. Normally, a method with this annotation behaves like committing if it works normally, rolling back if it does something unexpected, and so on. However, in the case of test code, if you rewrite the database after each test, the reproducibility of the test will be lost, so by default it will be rolled back every time the method is executed.
By combining the above two annotations, you can manually back up the database, stuff records from files, put them back at the end, and so on.

When you actually run the test, you can get the list of user entities with Dao's method and verify that you get the expected result (list size = number of records).


By verifying the SELECT process, I was able to cover the basics of the database operation test code to some extent, so I would like to take a look at other processes at once.

Supplement: Where should the EntityManager be set? It's a bit different from the test itself, but in my personal experience, the EntityManager was often null and the test didn't work. I was aware that if I declare EntityManager in the field of Dao class and add `PersistenceContext` annotation, it will work for the time being, so I could not solve it, and I was addicted to it.
The actual movement is that a bean called `LocalContainerEntityManagerFactoryBean` initializes the EntityManagerFactory and creates an EntityManager by DI. At this time, a DI container is required, but in Spring, ApplicationContext seems to play that role.
Since it works as above, even if you get EntityManager with Autowired or PersistenceContext annotation in the test class and pass it to the test target class, it will work for the time being. However, in that case, the operation on the actual application and the operation on the test code will be different, so I personally think that it is better to use it in the following form.
-Create Dao base class and set EntityManager in the base class field. (At this time, add PersistenceContext annotation) -In the base class, only the getter of EntityManager is exposed, and in the derived class, only the getter is restricted. -Define basic methods (merge, persist, etc.) using EntityManager on the base class side. There is not much information about EntityManager, and I was addicted to trying to write it poorly, so I think it is better to leave it to Spring at first. If it gets stuck in the test code and the implementation does not proceed at all, it may not be good for mental health, so I will write it as a memorandum. [Reference](https://builder.japan.zdnet.com/sp_oracle/35067018/)
* When using Repository, EntityManager is completely hidden, so you probably don't need to be aware of it.
[Dao base class](https://github.com/a-pompom/SpringBoot-MVC-Test/blob/master/dbMVCTest/src/main/java/app/db/dao/BaseDao.java) [Dao derived class](https://github.com/a-pompom/SpringBoot-MVC-Test/blob/master/dbMVCTest/src/main/java/app/db/dao/UserDao.java)

Test CRUD processing for databases

Of the CRUD processing, we were able to verify SELECT, so let's look at the remaining update / creation processing. I think that you can understand the outline with the knowledge you have acquired so far, so the actual test code is described below.

CRUDDaoTest.java(Excerpt)


//Processing to reflect the state after executing the test method in the database
	//Normally, update processing is synchronized with the database when the transaction is committed,
	//Explicitly synchronize because it does not commit in the test process
	@AfterEach
	void tearDown() {
		userDao.getEm().flush();
	}
	
	/**
	 *Verify if a new record is created by the create process
	 *Verify that the DB was rewritten as expected by the entity by comparing it with the Expected Database
	 */
	@Test
	@DatabaseSetup(value = "/CRUD/setUp/forCreate")
	@ExpectedDatabase(value = "/CRUD/create/", assertionMode=DatabaseAssertionMode.NON_STRICT)
A new user is created with the void create method() {
		User user = new User();
		user.setUserName("test3");
		
		userDao.saveOrUpdate(user);
	}
	
	/**
	 *Verify if the existing record is updated by the update process
	 *Verify that the DB was rewritten as expected by the entity by comparing it with the Expected Database
	 */
	@Test
	@DatabaseSetup(value = "/CRUD/setUp/")
	@ExpectedDatabase(value = "/CRUD/update/", assertionMode=DatabaseAssertionMode.NON_STRICT)
User 1 can be rewritten with the void update method() {
		User user = new User();
		user.setUserId(1L);
		user.setUserName("test1mod");
		
		userDao.saveOrUpdate(user);
	}
	
	/**
	 *Verify if the record is deleted by the delete process
	 *Prepare a DB before and after processing, and verify the validity by comparing whether the expected result will be obtained after deletion.
	 */
	@Test
	@DatabaseSetup(value = "/CRUD/setUp/")
	@ExpectedDatabase(value = "/CRUD/delete/", assertionMode=DatabaseAssertionMode.NON_STRICT)
User 1 can be deleted with the void delete method() {
		userDao.delete(1);
	}

Now that we have some new ones, let's take a quick look at each one. Also, there are some caveats about the test code for CURD processing of database operations, so let's take a look at those as well.

AfterEach

This is an annotation for JUnit5 and describes the process you want to insert after executing each test method. Here, the flush method of EntityManager is explicitly called. The flush method is doing the work to synchronize the entities in the persistence context with the records in the database. Normally, this method is automatically called when the transaction is committed, without even being aware of it. Reference

However, in this test code, we need to RollBack to restore the database after the database operation is finished. Then, the flush method will not be called, so the expected result of the test method will not be reflected in the database, and the test will not pass. There are several ways to deal with it, but it seems better to explicitly call the flush method when each method commits a transaction, that is, when the process is completed, just like running an app.

From the above, by calling the flush method after executing each test method, the expected result can be verified correctly.

ExpectedDatabase

It is used at the same time as the DatabaseSetup annotation, and as the name implies, it is for verifying the state of the database after executing the test method. As with the DatabaseSetup annotation, specify the directory where the CSV file that describes the table state of the expected result is stored in the value value. Furthermore, the property "assertionMode" is set, but by setting "NON_STRICT" here, only the columns specified in the CSV file will be verified, not all columns.

Transactional annotation

In this test class, Transactional annotation is added at the class level. Setting this annotation at the class level is the same as annotating all the methods in the class. In some Controller tests, transaction control is not required, but if you try to set each method every time, omissions will occur, so it is better to set them collectively at the class level.


Now you should have some understanding of the processing required for CRUD processing. At the end of Level 2, here are some things to keep in mind when testing database operations (because I'm addicted to it). I hope it will be helpful for you.

About the description method of update / delete process

The above process is a process to rewrite an existing record. I think that it may process multiple records, but in many cases, Web applications will target one record. At this time, key information is required to clarify the processing target.

I think there are various ways to do it, but I think it's simple and easy to write "specify the ID in the record of the CSV file". The point to note here is the concept of logic to realize the test. Since it seems to be a little long, I wrote it in the supplement, so please take a look if you are interested.

Supplement: How to implement the test By the way, regarding the above method of acquiring "key information", prepare a utility class for testing to make it easier to write a test, or add a method to the code on the application side to make it easier to test. There's nothing you can't do, but it's not recommended.
First of all, if you get stuck writing a test about creating a utility class to make it easier to write, you shouldn't think about `how to pass the test`. You should think about `can you write it simpler`? If you're writing a method class for testing from a simple CRUD processing level, it's easy to imagine what your test code will look like in a working-level application.
Also, it is often said that code that is easy to write tests is good code. This is not a story that you should write it considering passing the test on the application side, but it should prevent such things as "individual modules are tightly coupled and it becomes difficult to resolve dependencies at the time of testing". Thing. Not only is such an implementation difficult to test, but it also expands the scope of influence when expanding or changing, leading to the creation of unexpected bugs.
If you focus on test code, it will be closer to test code than implementation, and vice versa, it is a part that is hard to notice at first, but it may be good to take a breath and go back to the basics. not. I wrote it like that, but I was addicted to writing the test code myself, so I will write it as a self-discipline.

About ID value of create process

Consider the case of registering a new record in the database with a test method. For example, if the ID is automatically numbered, and the ID is assigned to the setup record, key duplication may occur.

In that case, if you set the ID that the record of the result set is also numbered ..., it is safer not to control the value that is automatically numbered.

As a solution, if you want to verify the generation of a new record, I think it is better to proceed with the policy of using a CSV file excluding the ID and verifying only the contents column without the ID involved.


A lot of new things have come out in level 2. However, if you get used to it to some extent, you can write crisply, and above all, start the server like a manual test, access the page, actually process it and go to see the DB ... I think that it is a very beneficial part because it can be verified without doing it. Therefore, even if you master the range up to level 2, the efficiency of defect correction during development will be greatly improved.

By completing Level 2, you should be able to:


By the way, Spring Boot is a framework for creating Web applications, so it is common to use POST requests when actually operating the database. Therefore, at Level 3, we would like to take a look at POST request validation. The level will go up, but it's something that you can fully understand with your knowledge so far, so I'd be happy if you could follow me to the end (╹◡╹).

Level3. Test POST request

Next, let's look at the test code for validating POST requests. Since it would be long to put the entire code to be verified, I would like to focus on the test code by describing only the outline of the application here.

Level 3 uses a to-do list as a subject. It's a simple one that can do the following simple CRUD processing.

image.png

There will be some new knowledge about POST requests, but if you have the knowledge so far, you can understand it, so please take a look at the test code after a comprehensive review of this article. The actual test code is shown below. It's a bit long, but most of it is understandable ... I'm happy.

TodoControllerTest.java


@DbUnitConfiguration(dataSetLoader = CsvDataSetLoader.class)
@TestExecutionListeners({
	  DependencyInjectionTestExecutionListener.class,
	  TransactionalTestExecutionListener.class,
	  DbUnitTestExecutionListener.class
	})
@AutoConfigureMockMvc
@SpringBootTest(classes = {DbMvcTestApplication.class})
@Transactional
public class TodoControllerTest {
	
	//mockMvc Mock object for handling Http request and response without deploying to Tomcat server
	@Autowired
	private MockMvc mockMvc;
	
	@Autowired
	private TodoDao todoDao;
	
	@AfterEach
	void tearDown() {
		todoDao.getEm().flush();
	}
	
	/**
	 *Verify that view is returned correctly
	 * @throws Exception
	 */
	@Test
Todo is passed as view in void init processing() throws Exception {
		this.mockMvc.perform(get("/todo/init"))
			.andExpect(status().isOk())
			.andExpect(view().name("todo"));
	}
	
	/**
	 *Verify if the record acquired from DB is set in the model
	 *This time it is not a complicated process, so if one record in the DB is passed to the model, it is considered to be operating normally.
	 * 
	 * @throws Exception
	 */
	@Test
	@DatabaseSetup(value = "/TODO/setUp/")
The void init process passes the existing task to the model() throws Exception {
		
		//With mockMvc/todo/Send a get request to "init"
		this.mockMvc.perform(get("/todo/init"))
		//DB records are passed to the model as a list
			.andExpect(model().attribute("todoForm", hasProperty(
					"todoList", hasItem(
							hasProperty(
									"task", is("task1")
							)
					)
			)));
	}
	
	/**
	 *Verify if new record is registered in DB from screen input
	 * @throws Exception
	 */
	@Test
	@DatabaseSetup(value = "/TODO/setUp/create")
	@ExpectedDatabase(value = "/TODO/create/", assertionMode=DatabaseAssertionMode.NON_STRICT)
A new task is registered in the DB by void save processing() throws Exception {
		
		this.mockMvc.perform(post("/todo/save")
			.contentType(MediaType.APPLICATION_FORM_URLENCODED)
			.param("newTask", "newTask"));
		
	}
	
	/**
	 *Verify if existing records are updated by screen input
	 *Since screen information is not used this time, it is not possible to obtain the ID that is automatically numbered.
	 *Therefore, this time, specify the update target manually.
	 *Basically, the order of the list is not guaranteed, so it seems necessary to sort it at the time of SELECT.
	 * @throws Exception
	 */
	@Test
	@DatabaseSetup(value = "/TODO/setUp/")
	@ExpectedDatabase(value = "/TODO/update/", assertionMode=DatabaseAssertionMode.NON_STRICT)
The void update process updates the existing task() throws Exception{
		
		//"To do" with mockMvc/Send a post request to "update"
		long updateTargetId = 3L;
		int updateTargetIndex = 2;
		
		this.mockMvc.perform(post("/todo/update/" + updateTargetIndex + "/" + updateTargetId)
				.param("todoList[" + updateTargetIndex + "].task", "task3mod")
				.contentType(MediaType.APPLICATION_FORM_URLENCODED)
				);
		
	}
	/**
	 *Verify if the task selected on the screen is deleted
	 * @throws Exception
	 */
	@Test
	@DatabaseSetup(value = "/TODO/setUp/")
	@ExpectedDatabase(value = "/TODO/delete/", assertionMode=DatabaseAssertionMode.NON_STRICT)
The void delete process deletes the existing task() throws Exception {
		long deleteTargetId = 3L;
		
		this.mockMvc.perform(post("/todo/delete/" + deleteTargetId)
				.contentType(MediaType.APPLICATION_FORM_URLENCODED)
				);
		
	}
}

In the following, we will describe the new POST request related methods and points to note when handling POST requests. I don't think it's scary because what you're doing is just setting parameters and making requests.

POST parameters

A notable point in the POST request test code is how to set the parameters passed to the request. However, if you understand the POST request to some extent, you can set it intuitively. For POST requests, we recommend it because it is clearly written in MDN.

By the way, in the test code using MockMvc, the part where the GET request was made by the perform method at level 1 will be changed to the POST request. After that, you can pass parameters in key-value format with the param method, so you can just make a request according to the form passed in the actual request. You can also call the param method in a GET request, but in that case it will be sent as a query parameter. Here, the POST request is submitted based on the form, so the parameters are stored in the request body.

Also, although the contentType works without specifying it, I think it is better to set it so that it is as close as possible to the actual POST request.


Things to keep in mind when testing POST requests

This time, we are mainly verifying whether the database is updated correctly by the POST request. At this point, the problem is what is processed by the POST request. I would like to take a quick look at each CRUD process.

When creating a new record, the parameters for the new record are independent of the table, so you shouldn't worry about it.

This time, the verification range is until the view name is passed to View, so it is OK if you can verify the object passed to Model. So there's nothing to worry about here either ...

Since the ID to be deleted is determined by the path of the request, it is necessary to clearly indicate the deletion target when creating a request with MockMvc. You should avoid these conventions in your "implementation", but I think you don't have to worry too much about them in your test code. In the first place, the test code defines the state of the database as "fixed", so rather than assuming changes and expansions, you should focus on the part of "whether a constant output is always obtained from a constant input". The strength of test code is that you can always get the same result no matter how many times you execute it, so I personally think that you should think about how to write it separately from implementation.

The same can be said for the update process. However, when updating, if you want to target one record in the list of entities, you need to take some measures such as separating the edit target from the list and storing it in a separate update entity. In this update process, the ID of the index entity of the list is fixed, but the order of the list is basically not guaranteed, so in the business level application, in the above form, "always It is necessary to create a state where the same result can be obtained from the same input. I would like to write about that when I get used to the screen test code (desire).


Although it was labeled as level 3, most of it was in the form of a comprehensive review so far, so I understand ... I understand ... !! I would be very happy if you could (:) Understanding Level 3 test code allows you to:


Summary

It's been longer than I expected, but now I've seen how to write test code for simple CRUD processing. When writing the implementation part, it is necessary to be aware of things that you would not normally be aware of, and I think there were some hard parts. However, writing test code will deepen your understanding of frameworks and languages, streamline development, and provide many benefits.

And best of all, the joy of passing a test is something you can't enjoy with implementation alone, and once you've overcome the first barrier, writing test code becomes a lot of fun.

Through this article, I might be able to write test code with Spring Boot ...? I would appreciate it if you could think. I'm still immature about test code, so I'd like to see more explanations about test code.

Recommended Posts

Write test code in Spring Boot
Let's write a test code for login function in Spring Boot
Test controller with Mock MVC in Spring Boot
Run a Spring Boot project in VS Code
Set context-param in Spring Boot
Spring Boot 2 multi-project in Gradle
Write Java8-like code in Java8
Major changes in Spring Boot 1.5
NoHttpResponseException in Spring Boot + WireMock
How to write a unit test for Spring Boot 2
[JUnit 5 compatible] Write a test using JUnit 5 with Spring boot 2.2, 2.3
[JUnit 5] Write a validation test with Spring Boot! [Parameterization test]
Test field-injected class in Spring boot test without using Spring container
Spring Boot Hello World in Eclipse
Spring Boot application development in Eclipse
Spring Boot application code review points
Java Spring environment in vs Code
Spring Boot programming with VS Code
Implement REST API in Spring Boot
What is @Autowired in Spring boot?
Use DBUnit for Spring Boot test
Implement Spring Boot application in Gradle
[RSpec] How to write test code
Thymeleaf usage notes in Spring Boot
Sample code that uses the Mustache template engine in Spring Boot
Sample code to unit test a Spring Boot controller with MockMvc
[Spring Boot] Until @Autowired is run in the test class [JUnit5]
Introduce RSpec and write unit test code
Launch (old) Spring Boot project in IntelliJ
Build Spring Boot + Docker image in Gradle
Static file access priority in Spring boot
Output Spring Boot log in json format
Local file download memorandum in Spring Boot
Perform transaction confirmation test with Spring Boot
Try using Spring Boot with VS Code
Create Java Spring Boot project in IntelliJ
Loosen Thymeleaf syntax checking in Spring Boot
Spring Boot @WebMvcTest test enables Spring Security default security
[Practice! ] Display Hello World in Spring Boot
Use DynamoDB query method in Spring Boot
To write Response data directly in Spring
Write code that is difficult to test
DI SessionScope Bean in Spring Boot 2 Filter
Form class validation test with Spring Boot
Change session timeout time in Spring Boot
Challenge Spring Boot
Efficient test code
Spring Boot Form
Spring Boot Memorandum
gae + spring boot
SameSite cookie in Spring Boot (Spring Web MVC + Tomcat)
About designing Spring Boot and unit test environment
Asynchronous processing with regular execution in Spring Boot
Output request and response log in Spring Boot
JUnit 5: How to write test cases in enum
Write RestController tests quickly with Spring Boot + Spock
Use Servlet filter in Spring Boot [Spring Boot 1.x, 2.x compatible]
How to add a classpath in Spring Boot
Java tips-Create a Spring Boot project in Gradle
How to write test code with Basic authentication
How to bind to property file in Spring Boot