post-image

Hướng dẫn test JUnit 5 với Mockito trong Spring Boot

Testing

Trong bài hướng dẫn này, tôi sẽ hướng dẫn bạn cách thực hiện kiểm tra tích hợp Spring Boot 2 với Mockito. Trong Java, JUnit là một Java Testing Framework được sử dụng rộng rãi trong các dự án Java. JUnit 5 là phiên bản mới của JUnit với mục đích hỗ trợ các tính năng mới ra mắt từ Java 8 trở về sau.

Các phiên bản hiện tại của JUnit 5 gồm có 3 module khác nhau từ 3 sub-project khác nhau:

Tổng hợp 200+ tài liệu, sách, bài thực hành, video hướng dẫn lập trình… từ cơ bản đến nâng cao

  • Thứ nhất là JUnit Platform: layer nền tảng cho phép những Testing Framework có thể chạy trên máy ảo JVM.
  • Thứ hai là JUnit Jupiter: đây chính là phiên bản JUnit 5 và được chạy trên JUnit Platform
  • Cuối cùng làJUnit Vintage: kế thừa từ TestEngine và cho phép chạy các phiên bản cũ hơn của JUnit.

1. Các tính năng của JUnit

  • JUnit là một framework mã nguồn mở, được sử dụng để viết và chạy kiểm thử.
  • Cung cấp các annotation để định nghĩa các phương thức kiểm thử.
  • Cung cấp các Assertion để kiểm tra kết quả mong đợi.
  • Test case JUnit có thể được chạy tự động.
  • Cung cấp các test runner để thực thi các test script.
  • Test case JUnit có thể được tổ chức thành các test suite.
  • JUnit cho thấy kết quả test một cách trực quan: pass (không có lỗi) là màu xanh và fail (có lỗi) là màu đỏ.
  • ….

THAM GIA KHÓA HỌC LẬP TRÌNH

2. Một số khái niệm cần biết trong Unit Test

  • Unit Test case: là 1 chuỗi code để đảm bảo rằng đoạn code được kiểm thử làm việc như mong đợi. Mỗi function sẽ có nhiều test case, ứng với mỗi trường hợp function chạy.
  • Setup: Đây là hàm được chạy trước khi chạy các test case, thường dùng để chuẩn bị dữ liệu để chạy test.
  • Teardown: Đây là hàm được chạy sau khi các test case chạy xong, thường dùng để xóa dữ liệu, giải phóng bộ nhớ.
  • Assert: Mỗi test case sẽ có một hoặc nhiều câu lệnh Assert, để kiểm tra tính đúng đắn của hàm.
  • Mock: là một đối tượng ảo, mô phỏng các tính chất và hành vi giống hệt như đối tượng thực được truyền vào bên trong khối mã đang vận hành nhằm kiểm tra tính đúng đắn của các hoạt động bên trong. Giả sử chương trình của chúng ta được chia làm 2 module: A và B. Module A đã code xong, B thì chưa. Để test module A, ta dùng mock để làm giả module B, không cần phải đợi tới khi module B code xong mới test được.
  • Test Suite : Test suite là một tập các test case và nó cũng có thể bao gồm nhiều test suite khác, test suite chính là tổ hợp các test.

Trong bài hướng dẫn này, tôi sẽ hướng dẫn bạn cách thực hiện kiểm tra tích hợp Spring Boot 2 với Mockito.

  • Spring Boot 2.1.2.RELEASE
  • JUnit 5
  • Mockito 2
  • Maven 3

Hãy xem ứng dụng web Spring Boot MVC sau đây và cách thực hiện kiểm tra các đơn vị với JUnit 5 và mô phỏng với Mockito Framework.

TÀI LIỆU HỌC LẬP TRÌNH

3. Maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com</groupId>
	<artifactId>hdd</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>hdd</name>
	<description>Demo project for Spring Boot JUnit 5</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>

		<!-- mvc -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- exclude junit 4 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>junit</groupId>
					<artifactId>junit</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<!-- junit 5 -->
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-engine</artifactId>
			<version>${junit-jupiter.version}</version>
			<scope>test</scope>
		</dependency>

		<!-- mockito + junit 5 -->
		<!-- exclude this, mockito still ok with junit 5, why need this? -->
		<!--
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>${mockito.version}</version>
            <scope>test</scope>
        </dependency>
        -->

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
Code language: HTML, XML (xml)

Các dependency của project:

$ mvn dependency:tree

[INFO] com:hdd:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.5.3:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.5.3:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.5.3:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.4:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.4:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.14.1:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.14.1:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.32:compile
[INFO] |  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.28:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-json:jar:2.5.3:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.12.4:compile
[INFO] |  |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.12.4:compile
[INFO] |  |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.12.4:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.12.4:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.12.4:compile
[INFO] |  |  \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.12.4:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.5.3:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.50:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.50:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.50:compile
[INFO] |  +- org.springframework:spring-web:jar:5.3.9:compile
[INFO] |  |  \- org.springframework:spring-beans:jar:5.3.9:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:5.3.9:compile
[INFO] |     +- org.springframework:spring-aop:jar:5.3.9:compile
[INFO] |     +- org.springframework:spring-context:jar:5.3.9:compile
[INFO] |     \- org.springframework:spring-expression:jar:5.3.9:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.5.3:test
[INFO] |  +- org.springframework.boot:spring-boot-test:jar:2.5.3:test
[INFO] |  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.5.3:test
[INFO] |  +- com.jayway.jsonpath:json-path:jar:2.5.0:test
[INFO] |  |  +- net.minidev:json-smart:jar:2.4.7:test
[INFO] |  |  |  \- net.minidev:accessors-smart:jar:2.4.7:test
[INFO] |  |  |     \- org.ow2.asm:asm:jar:9.1:test
[INFO] |  |  \- org.slf4j:slf4j-api:jar:1.7.32:compile
[INFO] |  +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test
[INFO] |  |  \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test
[INFO] |  +- org.assertj:assertj-core:jar:3.19.0:test
[INFO] |  +- org.hamcrest:hamcrest:jar:2.2:test
[INFO] |  +- org.junit.jupiter:junit-jupiter:jar:5.7.2:test
[INFO] |  |  \- org.junit.jupiter:junit-jupiter-params:jar:5.7.2:test
[INFO] |  +- org.mockito:mockito-core:jar:3.9.0:test
[INFO] |  |  +- net.bytebuddy:byte-buddy:jar:1.10.22:test
[INFO] |  |  +- net.bytebuddy:byte-buddy-agent:jar:1.10.22:test
[INFO] |  |  \- org.objenesis:objenesis:jar:3.2:test
[INFO] |  +- org.mockito:mockito-junit-jupiter:jar:3.9.0:test
[INFO] |  +- org.skyscreamer:jsonassert:jar:1.5.0:test
[INFO] |  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] |  +- org.springframework:spring-core:jar:5.3.9:compile
[INFO] |  |  \- org.springframework:spring-jcl:jar:5.3.9:compile
[INFO] |  +- org.springframework:spring-test:jar:5.3.9:test
[INFO] |  \- org.xmlunit:xmlunit-core:jar:2.8.2:test
[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.7.2:test
[INFO] |  +- org.apiguardian:apiguardian-api:jar:1.1.0:test
[INFO] |  +- org.junit.platform:junit-platform-engine:jar:1.7.2:test
[INFO] |  |  +- org.opentest4j:opentest4j:jar:1.2.0:test
[INFO] |  |  \- org.junit.platform:junit-platform-commons:jar:1.7.2:test
[INFO] |  \- org.junit.jupiter:junit-jupiter-api:jar:5.7.2:test
[INFO] \- org.springframework.boot:spring-boot-devtools:jar:2.5.3:compile (optional)
[INFO]    +- org.springframework.boot:spring-boot:jar:2.5.3:compile
[INFO]    \- org.springframework.boot:spring-boot-autoconfigure:jar:2.5.3:compile
[INFO] ------------------------------------------------------------------------
Code language: JavaScript (javascript)

4. Test Spring Boot + JUnit 5 + Mockito

4.1 Spring Boot

HelloWorldImpl.java:

package com.hdd.service.impl;

import com.hdd.repository.HelloWorldRepository;
import com.hdd.service.HelloWorldService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class HelloWorldServiceImpl implements HelloWorldService{

    @Autowired
    HelloWorldRepository helloWorldRepository;

    @Override
    public String get() {
        return helloWorldRepository.get();
    }
}
Code language: CSS (css)

HelloWorldRepositoryImpl.java

package com.hdd.repository;

import org.springframework.stereotype.Repository;

@Repository
public class HelloWorldRepositoryImpl implements HelloWorldRepository {
    @Override
    public String get() {
        return "Hello JUnit 5";
    }
}
Code language: CSS (css)

4.2 JUnit 5

HelloWorldServiceTest.java

package com.hdd.service;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertEquals;

//https://docs.spring.io/spring-boot/docs/2.1.2.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications
@SpringBootTest
public class HelloWorldServiceTest {

    @Autowired
    HelloWorldService helloWorldService;

    @DisplayName("Test Spring @Autowired Integration")
    @Test
    void testGet() {
        assertEquals("Hello JUnit 5", helloWorldService.get());
    }

}
Code language: JavaScript (javascript)

4.3 Mockito

HelloWorldServiceMockTest.java

package com.hdd.service;

import com.hdd.repository.HelloWorldRepository;
import com.hdd.service.impl.HelloWorldServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

//@ExtendWith(MockitoExtension.class) , need this? still able to run.
@SpringBootTest
public class HelloWorldServiceMockTest {

    @Mock
    private HelloWorldRepository helloWorldRepository;

    //@Spy
    @InjectMocks // auto inject helloWorldRepository
    private HelloWorldService helloWorldService = new HelloWorldServiceImpl();

    @BeforeEach
    void setMockOutput() {
        //when(helloService.get()).thenReturn("Hello Mockito");
        when(helloWorldRepository.get()).thenReturn("Hello Mockito From Repository");
    }

    @DisplayName("Test Mock helloWorldService + helloWorldRepository")
    @Test
    void testGet() {
        assertEquals("Hello Mockito From Repository", helloWorldService.get());
    }

}
Code language: JavaScript (javascript)

5. Test Spring MVC Controller

5.1 Controller

HelloWorldController.java:

package com.hdd.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloWorldController {

    @ResponseBody
    @GetMapping("/")
    public String hello() {
        return "Hello Controller";
    }

}
Code language: CSS (css)

5.2 JUnit 5 và MVC Test

HelloWorldControllerTest.java

package com.hdd.controller;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;

import java.net.URL;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloWorldControllerTest {
    // bind the above RANDOM_PORT
    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void getHello() throws Exception {

        ResponseEntity<String> response = restTemplate.getForEntity(new URL("http://localhost:" + port + "/").toString(), String.class);
        assertEquals("Hello Controller", response.getBody());

    }

}
Code language: JavaScript (javascript)

Oke đã xong run và xem kết quả nhé.

Chúc bạn thành công!

Source Code: Github

Các bài viết liên quan:

https://hocspringboot.net/2021/08/09/orm-la-gi-tong-quan-ve-orm-framework-2/

https://hocspringboot.net/2021/08/03/gioi-thieu-ve-spring-data-jpa/

https://hocspringboot.net/2021/08/01/spring-data-jpa/

>> Xem ngay Tài liệu Java Core giúp bạn “Nâng Cấp” kỹ năng lập trình

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *