Preface
Usually we write Spring projects using Java language for business development and Java for unit testing. But Java is not very efficient in writing test code due to its lengthy code, and we usually consider multiple scenarios when writing the test code, so the amount of code expands dramatically, which brings a lot of time wastage. The biggest headache is the MockMvc mock request test, Java does not support multi-line strings until 15, which leads to the need to splice line by line, which is very unintuitive to read and does not make good use of the Intellij IDEA injection language.
So what can we do to solve these problems?
Thinking
We know that Java runs on top of the Java Virtual Machine (JVM), which is inherently language-agnostic, and can run on the JVM regardless of the language the upper layer is written in, as long as it can be compiled into a bytecode file. So the JVM is not just a program that can run Java. For example, Kotlin, Scala, and Groovy can all run on the JVM. In addition to running different code, languages can also call each other because the code is compiled and then has no relation to the higher-level language (we are all bytecode, so why can’t we work together 🤣).
So in this way, we can write test code in a more intuitive and convenient language. Kotlin and Groovy have good compatibility with Spring, and we can use both languages to write test code (this article uses Kotlin).
Implementation
Creating a project
First, we need to create a project (of course, you can also modify on the existing project), the creation of the same way as the pure Java project can be created.
We need to configure the Maven dependencies and plugins, i.e. the pom.xml
file, according to Kotlin’s documentation.
First modify the properties
property, the same as Spring was created with a java.version
, we need to add a kotlin.version
property. Separating out the versions will help with subsequent maintenance.
1
2
3
4
|
<properties>
<java.version>11</java.version>
<kotlin.version>1.5.0</kotlin.version>
</properties>
|
Then add the dependencies for the Kotlin standard library. Note that we only need to use Kotlin in our test environment, so define scope
as test
.
1
2
3
4
5
6
|
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
|
Next is to configure the Maven plugin for compiling Kotlin code, copy the following code directly into the build.plugins
tag. Be careful not to delete Spring’s Maven.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
|
At this point, the project has the ability to compile Kotlin. However, we have not imported any Kotlin test libraries yet, so it is not convenient to use JUnit only, so we can add some assertion libraries for writing tests easily.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<dependency>
<groupId>io.kotest</groupId>
<artifactId>kotest-runner-junit5-jvm</artifactId>
<version>4.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.kotest</groupId>
<artifactId>kotest-assertions-core-jvm</artifactId>
<version>4.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.kotest</groupId>
<artifactId>kotest-assertions-json</artifactId>
<version>4.5.0</version>
<scope>test</scope>
</dependency>
|
Kotest is also a unit testing tool similar to JUnit, but we will only use the assertion feature, because Kotest has Spring testing support, but you may encounter a lot of strange problems (at least I didn’t get it right, I might as well just use JUnit).
Writing business
With the test environment configured, we can start writing the business code. This article does not use the actual project for testing, just write a random controller and a few functions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@RestController
public class IndexController {
@GetMapping("/index")
public String get() {
return "index";
}
@PostMapping("/index")
public String post(@RequestParam("value") final String value) {
return value;
}
@PutMapping("/json")
public JsonNode json(@RequestBody JsonNode node) {
return node;
}
}
|
1
2
3
4
5
6
7
|
@Service
public class UserService {
public String getUserName(final Long id) {
return "username: " + id;
}
}
|
Writing tests
We need to create a kotlin
folder in the test
folder to store the Kotlin test code (you can write it directly in the java folder, but it’s better to standardize it), and then set kotlin
as the test folder (IDEA will not recognize it automatically).
The code used to test the UserService
is as follows.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@SpringBootTest
class UserServiceTest {
@Autowired
private lateinit var userService: UserService
@Test
fun getUserName() {
userService.getUserName(1) shouldBe "username: 1"
userService.getUserName(2) should {
it shouldBe "username: 2"
it.length shouldBe 11
}
}
}
|
You can see that Kotlin provides a lot of syntactic sugar to avoid writing unintuitive and repetitive code when testing. Java’s code is a bit less intuitive in comparison.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void getUserName() {
assertEquals("username: 1", userService.getUserName(1L));
final String userName = userService.getUserName(2L);
assertEquals("username: 2", userName);
assertEquals(11, userName.length());
}
}
|
MockMvc testing
Java is fine for normal unit test code, but the advantages of Kotlin come into play in MockMvc testing. Spring provides a lot of DSL support for Kotlin.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
@WebMvcTest(IndexController::class)
class MockMvcTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
fun get() {
mockMvc.get("/index").andExpect {
status { isOk() }
content {
contentTypeCompatibleWith("text/plain")
string("index")
}
}
}
@Test
fun post() {
mockMvc.post("/index") {
param("value", "test value")
}.andExpect {
status { isOk() }
content {
string("test value")
}
}.andDo {
print()
handle {
println(it.response.characterEncoding)
}
}
}
@Test
fun json() {
mockMvc.put("/json") {
contentType = MediaType.APPLICATION_JSON
accept = MediaType.APPLICATION_JSON
content = """
{
"key": "value",
"key2": {
"key3": [1, 2, 3]
}
}
""".trimIndent()
}.andExpect {
status { isOk() }
content {
contentType(MediaType.APPLICATION_JSON)
}
jsonPath("$.key") {
value("value")
}
jsonPath("$.key2.key3.length()") {
value(3)
}
}
}
}
|
Reference https://blog.ixk.me/post/writing-spring-tests-with-kotlin