CyStack logo
  • Sản phẩm & Dịch vụ
  • Giải pháp
  • Bảng giá
  • Công ty
  • Tài liệu
Vi

vi

Trang chủHướng dẫnCách Mock đối tượng Dependency Injection trong Unit Test
Java

Cách Mock đối tượng Dependency Injection trong Unit Test

CyStack blog 5 phút để đọc
CyStack blog10/07/2025
Locker Avatar

Chris Pham

Technical Writer

Locker logo social
Reading Time: 5 minutes

Trong bài viết hôm nay, chúng ta sẽ học cách mock dependency injection object trong unit test bằng  @InjectMocks – một trong những annotation mạnh mẽ và tiện lợi nhất của Mockito.

Mock đối tượng Dependency Injection

@InjectMocks giúp chúng ta “tiêm” (inject) các dependency đã được mock vào đối tượng của lớp cần test một cách tự động như thế nào, qua đó đơn giản hóa đáng kể quá trình thiết lập test và giảm thiểu boilerplate code. Chúng ta cũng sẽ tìm hiểu về các cơ chế injection khác nhau mà Mockito sử dụng và cách áp dụng chúng trong thực tế.

Tiêm Dependency tự động như thế nào?

Annotation @InjectMocks của Mockito cho phép chúng ta tự động tiêm (inject) các dependency đã được mock (hoặc spy) vào một đối tượng của lớp được đánh dấu bằng chính @InjectMocks. Điều này cực kỳ hữu ích khi lớp mà chúng ta muốn kiểm thử (System Under Test – SUT) có các dependency bên ngoài cần được cô lập hoặc kiểm soát hành vi trong quá trình test. Các đối tượng mock được tiêm vào sẽ được khai báo bằng @Mock hoặc @Spy.

Các cơ chế Injection của Mockito

Mockito sẽ cố gắng tiêm các dependency đã được mock theo ba cách tiếp cận, với một thứ tự ưu tiên nhất định:

  1. Constructor Based Injection (Tiêm qua Constructor): Đây là phương pháp ưu tiên hàng đầu. Khi có một constructor được định nghĩa cho lớp, Mockito sẽ cố gắng tiêm các dependency bằng cách sử dụng constructor có số lượng tham số lớn nhất (hoặc constructor phù hợp nhất với các mock có sẵn). Nếu có nhiều constructor với cùng số lượng tham số, Mockito có thể gặp khó khăn hoặc cần tên mock để phân biệt.
  2. Setter Methods Based Injection (Tiêm qua Setter): Nếu không có constructor nào phù hợp hoặc không có constructor nào được định nghĩa, Mockito sẽ chuyển sang cố gắng tiêm các dependency thông qua các phương thức setter (ví dụ: setEmailService(EmailService emailService)).
  3. Field Based Injection (Tiêm trực tiếp vào Trường): Cuối cùng, nếu không có constructor hay phương thức setter nào khả dụng để tiêm dependency, Mockito sẽ cố gắng tiêm trực tiếp vào các trường (fields) của lớp.

Quy tắc tìm mock:

  • Nếu chỉ có một đối tượng mock khớp với kiểu dữ liệu của dependency, Mockito sẽ tiêm đối tượng đó vào.
  • Nếu có nhiều hơn một đối tượng mock cùng kiểu dữ liệu, Mockito sẽ sử dụng tên của đối tượng mock để khớp và tiêm dependency. Ví dụ, nếu một lớp có private EmailService primaryEmailService; và bạn có @Mock EmailService primaryEmailService;, Mockito sẽ cố gắng khớp theo tên.

Ví dụ về @InjectMocks

Để thấy rõ cách Mockito thực hiện việc tiêm dependency của các mock, chúng ta hãy tạo một số service và các lớp ứng dụng có dependency.

Các lớp Service

Đầu tiên là một interface Service và hai lớp triển khai đơn giản:

package com.journaldev.injectmocksservices;

public interface Service {

	public boolean send(String msg);
}

package com.journaldev.injectmocksservices;

public class EmailService implements Service {

	@Override
	public boolean send(String msg) {
		System.out.println("Sending email");
		return true;
	}

}

package com.journaldev.injectmocksservices;

public class SMSService implements Service {

	@Override
	public boolean send(String msg) {
		System.out.println("Sending SMS");
		return true;
	}
}

Tạo lớp App Service với Dependencies

Tiếp theo, chúng ta sẽ tạo ba lớp AppServices khác nhau, mỗi lớp đại diện cho một cách Mockito có thể tiêm dependency:

  • AppServices: Sử dụng Constructor Based Injection.
package com.journaldev.injectmocksservices;

// Dành cho Constructor Based @InjectMocks injection
public class AppServices {

	private EmailService emailService;
	private SMSService smsService;

	public AppServices(EmailService emailService, SMSService smsService) {
		this.emailService = emailService;
		this.smsService = smsService;
	}

	public boolean sendSMS(String msg) {
		return smsService.send(msg);
	}

	public boolean sendEmail(String msg) {
		return emailService.send(msg);
	}
}

  • AppServices1: Sử dụng Property Setter Based Injection.
package com.journaldev.injectmocksservices;

// Dành cho Property Setter Based @InjectMocks injection
public class AppServices1 {

	private EmailService emailService;
	private SMSService smsService;

	public void setEmailService(EmailService emailService) {
		this.emailService = emailService;
	}

	public void setSmsService(SMSService smsService) {
		this.smsService = smsService;
	}

	public boolean sendSMS(String msg) {
		return smsService.send(msg);
	}

	public boolean sendEmail(String msg) {
		return emailService.send(msg);
	}

}

  • AppServices2: Sử dụng Field Based Injection.
package com.journaldev.injectmocksservices;

// Dành cho Field Based @InjectMocks injection
public class AppServices2 {

	private EmailService emailService;
	private SMSService smsService;

	public boolean sendSMS(String msg) {
		return smsService.send(msg);
	}

	public boolean sendEmail(String msg) {
		return emailService.send(msg);
	}

}

Các ví dụ test với @InjectMocks

Giờ là lúc chúng ta viết các test case để xem @InjectMocks hoạt động như thế nào với từng kiểu injection. Lưu ý rằng lớp test của tôi là MockitoInjectMocksExamples đang extend BaseTestCase. Điều này nhằm mục đích khởi tạo các mock của Mockito trước khi các test chạy. Đây là mã của lớp BaseTestCase:

package com.journaldev.mockito.injectmocks;

import org.junit.jupiter.api.BeforeEach;
import org.mockito.MockitoAnnotations;

class BaseTestCase {

	@BeforeEach
	void init_mocks() {
		MockitoAnnotations.initMocks(this);
	}

}

Rất quan trọng: Nếu bạn không gọi MockitoAnnotations.initMocks(this); (hoặc sử dụng @RunWith(MockitoJUnitRunner.class) nếu dùng JUnit 4, hoặc @ExtendWith(MockitoExtension.class) nếu dùng JUnit 5), bạn sẽ gặp lỗi NullPointerException vì các đối tượng mock sẽ không được khởi tạo.

Và đây là lớp test chính của chúng ta:

MockitoInjectMocksExamples

package com.journaldev.mockito.injectmocks;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import com.journaldev.injectmocksservices.AppServices;
import com.journaldev.injectmocksservices.AppServices1;
import com.journaldev.injectmocksservices.AppServices2;
import com.journaldev.injectmocksservices.EmailService;
import com.journaldev.injectmocksservices.SMSService;

class MockitoInjectMocksExamples extends BaseTestCase {

	@Mock EmailService emailService;

	@Mock SMSService smsService;

	@InjectMocks AppServices appServicesConstructorInjectionMock;

	@InjectMocks AppServices1 appServicesSetterInjectionMock;

	@InjectMocks AppServices2 appServicesFieldInjectionMock;

	@Test
	void test_constructor_injection_mock() {
		// Thiết lập hành vi cho emailService và smsService đã được inject vào appServicesConstructorInjectionMock
		when(appServicesConstructorInjectionMock.sendEmail("Email")).thenReturn(true);
		when(appServicesConstructorInjectionMock.sendSMS(anyString())).thenReturn(true);

		// Kiểm tra các trường hợp đã được stub
		assertTrue(appServicesConstructorInjectionMock.sendEmail("Email"));
		// Kiểm tra trường hợp chưa được stub, mặc định Mockito trả về false cho boolean
		assertFalse(appServicesConstructorInjectionMock.sendEmail("Unstubbed Email"));

		assertTrue(appServicesConstructorInjectionMock.sendSMS("SMS"));

	}
}

@InjectMocksSetter Methods Injection Example

Tiếp theo là test case cho phương thức tiêm qua setter. Test này nằm trong cùng lớp MockitoInjectMocksExamples.

@Test
void test_setter_injection_mock() {
	// Thiết lập hành vi cho emailService và smsService đã được inject vào appServicesSetterInjectionMock
	when(appServicesSetterInjectionMock.sendEmail("New Email")).thenReturn(true);
	when(appServicesSetterInjectionMock.sendSMS(anyString())).thenReturn(true);

	// Kiểm tra các trường hợp đã được stub
	assertTrue(appServicesSetterInjectionMock.sendEmail("New Email"));
	assertFalse(appServicesSetterInjectionMock.sendEmail("Unstubbed Email"));

	assertTrue(appServicesSetterInjectionMock.sendSMS("SMS"));

}

@InjectMocksField Based Injection Example

Và cuối cùng là test case cho phương thức tiêm trực tiếp vào trường (field). Test này cũng nằm trong cùng lớp MockitoInjectMocksExamples.

@Test
void test_field_injection_mock() {
	// Thiết lập hành vi cho emailService và smsService đã được inject vào appServicesFieldInjectionMock
	when(appServicesFieldInjectionMock.sendEmail(anyString())).thenReturn(true);
	when(appServicesFieldInjectionMock.sendSMS(anyString())).thenReturn(true);

	// Với anyString(), mọi chuỗi đều được trả về true
	assertTrue(appServicesFieldInjectionMock.sendEmail("Email"));
	assertTrue(appServicesFieldInjectionMock.sendEmail("New Email"));

	assertTrue(appServicesFieldInjectionMock.sendSMS("SMS"));

}

Bạn có thể thấy, dù ba lớp AppServices có cấu trúc khác nhau để nhận dependency, Mockito với @InjectMocks vẫn “hiểu” và tiêm các mock tương ứng vào một cách mượt mà. Điều này giúp chúng ta tập trung vào việc định nghĩa hành vi của các mock mà không phải lo lắng về việc khởi tạo và nối kết chúng với lớp cần test.

Kết Luận

Qua bài viết này, chúng ta đã cùng nhau khám phá sâu hơn về annotation @InjectMocks của Mockito – một công cụ vô cùng mạnh mẽ giúp đơn giản hóa quá trình viết unit test trong Java. Chúng ta đã hiểu rõ cách @InjectMocks tự động tiêm các dependency đã được mock thông qua ba cơ chế ưu tiên: Constructor, Setter, và Field.

Hãy áp dụng kiến thức này vào các dự án thực tế của bạn để xây dựng các bộ unit test mạnh mẽ, hiệu quả hơn. Việc làm chủ Mockito và các kỹ thuật mocking sẽ nâng cao đáng kể chất lượng code và quy trình phát triển phần mềm của bạn.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất