GP Coder

Trang chia sẻ kiến thức lập trình Java

  • Java Core
    • Basic Java
    • OOP
    • Exception Handling
    • Multi-Thread
    • Java I/O
    • Networking
    • Reflection
    • Collection
    • Java 8
  • Design pattern
    • Creational Pattern
    • Structuaral Pattern
    • Behavior Pattern
  • Web Service
    • SOAP
    • REST
  • JPA
  • Java library
    • Report
    • Json
    • Unit Test
  • Message Queue
    • ActiveMQ
    • RabbitMQ
  • All
Trang chủ Java library Unit Test Mockito – Control mock’s behavior

Mockito – Control mock’s behavior

Đăng vào 01/04/2019 . Được đăng bởi GP Coder . 5962 Lượt xem . Toàn màn hình

Toàn bộ ý tưởng của việc tạo một mock object là có thể kiểm soát behavior của nó. Nếu một phương thức của mock được gọi, nó sẽ xử lý theo cách mà chúng ta có thể điều khiển được. Trong bài viết này, chúng ta sẽ cùng tìm hiểu các cách để điều khiển behavior của một đối tượng giả (mock object).

Nội dung

  • 1 Stubbing Methods – when().thenXxx()
  • 2 Stubbing Methods – doXxx().when()
  • 3 Argument matchers

Stubbing Methods – when().thenXxx()

Chúng ta muốn điều khiển một mock object và xác định phải làm gì khi các phương thức cụ thể của mock object được gọi. Điều này được gọi là Stubbing.

Mockito.when(T methodCall): dùng để giả lập một lời gọi hàm nào đó được sử dụng bên trong method đang được kiểm thử.

Phương thức Mockito.when() thường đi kèm với thenReturn(), thenAnswer(), … để chỉ định kết quả trả về. Ý nghĩa của cấu trúc này đơn giản là: “When the x method is called then return y” – “Khi một phương thức x được gọi thì return về một giá trị y”.

  • thenReturn() : chỉ định trả về một giá trị cụ thể.
  • thenThrow() : chỉ định trả về một Exception.
  • thenAnswer(): thực hiện xử lý các lệnh định nghĩa bên trong phương thức answer().
  • thenCallRealMethod(): chỉ định phương thức thực sự được gọi. Khi sử dụng phương thức này chúng ta nên chắc chắn rằng nó an toàn, bởi vì implement thực sự của phương thức nếu throw một exception hay phụ thuộc vào trạng thái cụ thể của object có thể xảy ra lỗi, không thể thực thi được.

Ví dụ Stubbing Methods với when().thenXxx()

CustomList.java


package com.gpcoder.mockito.whenthen;

import java.util.ArrayList;
import java.util.List;

public class CustomList {

	private List<String> list;

	public boolean add(String item) {
		getList().add(item);
		return true;
	}

	public String get(int index) {
		return getList().get(index);
	}

	public int size() {
		return getList().size();
	}

	public int clear() {
		getList().clear();
		return getList().size();
	}

	private List<String> getList() {
		if (list == null) {
			list = new ArrayList<>();
		}
		return list;
	}
}

WhenThenTest.java


package com.gpcoder.mockito.whenthen;

import java.util.Arrays;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class WhenThenTest {

	private CustomList mockedObject;

	@Before
	public void prepareForTest() {
		// Mock creation
		mockedObject = Mockito.mock(CustomList.class);

	}

	@Test
	public void thenReturnTest() {
		// Configure mock to return a specific value on a method call
		Mockito.when(mockedObject.get(0)).thenReturn("gpcoder.com");

		// Verify behavior
		Assert.assertEquals("gpcoder.com", mockedObject.get(0));
	}

	@Test(expected = IllegalStateException.class)
	public void thenThrowTest() {
		// Configure mock to throw an exception on a method call
		Mockito.when(mockedObject.add(Mockito.anyString())).thenThrow(IllegalStateException.class);

		mockedObject.add("gpcoder.com");
	}

	@Test
	public void thenAnswerTest1() {
		// Configure mock method call with custom Answer
		Mockito.when(mockedObject.get(Mockito.anyInt())).thenAnswer(new Answer<String>() {
			public String answer(InvocationOnMock invocation) {
				Object[] args = invocation.getArguments();
				// Object mockedObject = invocation.getMock();
				return "gpcoder.com" + Arrays.toString(args);
			}
		});

		// Verify behavior
		Assert.assertEquals("gpcoder.com[1]", mockedObject.get(1));
	}

	@Test
	public void thenAnswerTest2() {
		// Configure mock method call with custom Answer using Java 8 syntax
		Mockito.when(mockedObject.get(Mockito.anyInt())).thenAnswer(invocation -> {
			Object[] args = invocation.getArguments();
			// Object mockedObject = invocation.getMock();
			return "gpcoder.com" + Arrays.toString(args);
		});

		// Verify behavior
		Assert.assertEquals("gpcoder.com[1]", mockedObject.get(1));
	}

	@Test
	public void thenCallRealMethodTest() {
		// Configure mock method call real method
		// Be sure the real implementation is 'safe'.
		// If real implementation throws exceptions or depends on specific state of the
		// object then you're in trouble.
		Mockito.when(mockedObject.add(Mockito.anyString())).thenCallRealMethod();
		Mockito.when(mockedObject.get(Mockito.anyInt())).thenCallRealMethod();
		Mockito.when(mockedObject.size()).thenCallRealMethod();

		mockedObject.add("gpcoder.com");
		mockedObject.clear(); // This method will be not called on mocked object

		// Verify behavior
		Assert.assertEquals(1, mockedObject.size());
		Assert.assertEquals("gpcoder.com", mockedObject.get(0));
	}
}

Lưu ý: Với @Spy object, mặc định các phương thức sẽ được gọi thực sự, khi cần chỉ định kết quả trả về chúng ta sẻ sử dụng cấu trúc when().thenXxx(). Ngược lại với Spy, với @Mock object, mặc định các phương thức sẽ không được gọi, khi cần thực thi phương thức thực sự, chúng ta sẽ sử dụng cấu trúc when().thenCallRealMethod().

Ví dụ gọi nhiều thenReturn() liên tiếp nhau – Stubbing consecutive calls (iterator-style stubbing)


package com.gpcoder.mockito.whenthen;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

public class IteratorThenReturnTest {

	private CustomList mockedObject;

	@Before
	public void prepareForTest() {
		// Mock creation
		mockedObject = Mockito.mock(CustomList.class);
	}

	@Test
	public void consecutiveStubbingTest1() {
		// Configure mock to return a specific value on a method call
		Mockito.when(mockedObject.get(0)).thenReturn("one").thenReturn("two").thenReturn("three");

		// Verify behavior
		Assert.assertEquals("one", mockedObject.get(0));
		Assert.assertEquals("two", mockedObject.get(0));
		// From 3rd times, it always return "three" value
		Assert.assertEquals("three", mockedObject.get(0));
		Assert.assertEquals("three", mockedObject.get(0));
		Assert.assertEquals("three", mockedObject.get(0));
	}

	@Test
	public void consecutiveStubbingTest2() {
		// Configure mock to return a specific value on a method call
		Mockito.when(mockedObject.get(0)).thenReturn("one", "two", "three");

		// Verify behavior
		Assert.assertEquals("one", mockedObject.get(0));
		Assert.assertEquals("two", mockedObject.get(0));
		// From 3rd times, it always return "three" value
		Assert.assertEquals("three", mockedObject.get(0));
		Assert.assertEquals("three", mockedObject.get(0));
		Assert.assertEquals("three", mockedObject.get(0));
	}

	@Test
	public void overrideStubbingTest() {
		// Configure mock to return a specific value on a method call
		Mockito.when(mockedObject.get(0)).thenReturn("first");
		Mockito.when(mockedObject.get(0)).thenReturn("second");

		// Verify behavior
		// All mockedObject.get(0) calls will return "second"
		Assert.assertEquals("second", mockedObject.get(0));
		Assert.assertEquals("second", mockedObject.get(0));
	}
}

Ví dụ deep stubs method cho legacy code

Đôi khi trong hệ thống cũ, có những đoạn legacy code chúng ta cần phải viết test cho nó. Những đoạn code này đang vi phạm định luật Law of Demeter, ví dụ:


store.getOrder().getCustomer().getBillingAddress().getCity();

Để test được đoạn code trên chúng ta cần tạo mock object cho Store, sau đó sử dụng cấu trúc when(store.getOrder()).thenReturn(order) để trả về một mock object cho Order, tiếp tục when(order.getCustomer()).thenReturn(billingAddress) để trả về một mock object cho BillingAddress, cứ tiếp tục cho đến khi chỉ định được kết quả xử lý cho getCity().

Thật may mắn, Mockito hỗ trợ cho chúng ta một cách để có thể deep stubs và chỉ định kết quả mong muốn ở last mock chain thông qua phương thức tạo Mock Object:


mock(classToMock, RETURNS_DEEP_STUBS)

Ví dụ:

Store.java


package com.gpcoder.mockito.whenthen;

import lombok.Data;

@Data
class BillingAddress {
	private String city;
}

@Data
class Customer {
	private BillingAddress billingAddress;
}

@Data
class Order {
	private Customer customer;
}

@Data
public class Store {
	private Order order;
}

DeepStubTest.java


package com.gpcoder.mockito.whenthen;

import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;

public class DeepStubTest {

	@Test
	public void withoutDeepStubTest() {
		// Create mock object
		Store store = Mockito.mock(Store.class);
		Order order = Mockito.mock(Order.class);
		Customer customer = Mockito.mock(Customer.class);
		BillingAddress billingAddress = Mockito.mock(BillingAddress.class);

		// Configure mock to return a specific value on a method call
		Mockito.when(store.getOrder()).thenReturn(order);
		Mockito.when(order.getCustomer()).thenReturn(customer);
		Mockito.when(customer.getBillingAddress()).thenReturn(billingAddress);
		Mockito.when(billingAddress.getCity()).thenReturn("Can Tho");

		// Verify behavior
		Assert.assertEquals("Can Tho", store.getOrder().getCustomer().getBillingAddress().getCity());
	}

	@Test
	public void deepStubTest() {
		// Create mock object
		Store store = Mockito.mock(Store.class, Mockito.RETURNS_DEEP_STUBS);

		// Configure mock to return a specific value on a method call
		Mockito.when(store.getOrder().getCustomer().getBillingAddress().getCity()).thenReturn("Can Tho");

		// Verify behavior
		Assert.assertEquals("Can Tho", store.getOrder().getCustomer().getBillingAddress().getCity());
	}
}

Stubbing Methods – doXxx().when()

Cấu trúc doXxx().when() được sử dụng khi:

  • Chỉ định cách xử lý một phương thức void().
  • Chỉ định kết quả xử lý trên các phương thức của @Spy object.
  • Chỉ định các kết quả test khác nhau (nhiều hơn 1 lần trong khi thực thi 1 phương thức test).

Cấu trúc doXxx().when() tương tự như when().thenXxx(), nó cho cùng một kết quả. Chúng ta nên cố gắng sử dụng cấu trúc when().thenXxx() trong hầu hầu hết trường hợp, ngoại trừ những trường hợp đã liệt kê ở trên, bởi vì các argument của cấu trúc cấu trúc when().thenXxx() là type-safe và dễ đọc hơn.

  • doReturn() : chỉ định trả về một giá trị cụ thể.
  • doThrow() : chỉ định trả về một Exception.
  • doAnswer() : thực hiện xử lý các lệnh định nghĩa bên trong phương thức answer().
  • doNothing() : chỉ định phương thức này không làm gì.
  • doCallRealMethod() : chỉ định phương thức thực sự được gọi. Khi sử dụng phương thức này chúng ta nên chắc chắn rằng nó an toàn, bởi vì implement thực sự của phương thức nếu throw một exception hay phụ thuộc vào trạng thái cụ thể của object có thể xảy ra lỗi, không thể thực thi được.

Ví dụ:


package com.gpcoder.mockito.dothen;

import java.util.Arrays;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import com.gpcoder.mockito.whenthen.CustomList;

public class DoWhenTest {

	private CustomList mockedObject;

	@Before
	public void prepareForTest() {
		// Mock creation
		mockedObject = Mockito.mock(CustomList.class);
	}

	@Test
	public void doReturnTest() {
		// Configure mock to return a specific value on a method call
		Mockito.doReturn("gpcoder.com").when(mockedObject).get(0);

		// Verify behavior
		Assert.assertEquals("gpcoder.com", mockedObject.get(0));
	}

	@Test(expected = IllegalStateException.class)
	public void doThrowTest() {
		// Configure mock to throw an exception on a method call
		Mockito.doThrow(IllegalStateException.class).when(mockedObject).add(Mockito.anyString());

		mockedObject.add("gpcoder.com");
	}

	@Test
	public void doAnswerTest1() {
		// Configure mock method call with custom Answer
		Mockito.doAnswer(new Answer<String>() {
			public String answer(InvocationOnMock invocation) {
				Object[] args = invocation.getArguments();
				// Object mockedObject = invocation.getMock();
				return "gpcoder.com" + Arrays.toString(args);
			}
		}).when(mockedObject).get(Mockito.anyInt());

		// Verify behavior
		Assert.assertEquals("gpcoder.com[1]", mockedObject.get(1));
	}

	@Test
	public void doAnswerTest2() {
		// Configure mock method call with custom Answer using Java 8 syntax
		Mockito.doAnswer(invocation -> {
			Object[] args = invocation.getArguments();
			// Object mockedObject = invocation.getMock();
			return "gpcoder.com" + Arrays.toString(args);
		}).when(mockedObject).get(Mockito.anyInt());

		// Verify behavior
		Assert.assertEquals("gpcoder.com[1]", mockedObject.get(1));
	}

	@Test
	public void doCallRealMethodTest() {
		// Configure mock method call real method
		// Be sure the real implementation is 'safe'.
		// If real implementation throws exceptions or depends on specific state of the
		// object then you're in trouble.
		Mockito.doCallRealMethod().when(mockedObject).add(Mockito.anyString());
		Mockito.doCallRealMethod().when(mockedObject).get(Mockito.anyInt());
		Mockito.doCallRealMethod().when(mockedObject).size();

		mockedObject.add("gpcoder.com");
		mockedObject.clear(); // This method will be not called on mocked object

		// Verify behavior
		Assert.assertEquals(1, mockedObject.size());
		Assert.assertEquals("gpcoder.com", mockedObject.get(0));
	}

	@Test
	public void doNothingTest() {
		// Configure mock method call with custom Answer using Java 8 syntax
		Mockito.doNothing().when(mockedObject).remove(0);
		
		mockedObject.remove(0);

		// Verify behavior
		Mockito.verify(mockedObject, Mockito.times(1)).remove(0);
	}
}

Argument matchers

Các method Mockito.anyString(), Mockito.anyInt(), Mockito.any(),… thường được dùng khi mock các phương thức có tham số, khi mà chúng ta không xác định được giá trị của các tham số đó.

Ví dụ sử dụng Argurment Matcher


public class ArgumentMatcherTest {
	@Test
	public void anyIntTest() {
		List<String> mockedList = Mockito.mock(List.class);

		// Configure mock to return a specific value on a method call
		Mockito.when(mockedList.get(Mockito.anyInt())).thenReturn("gpcoder.com");
		Mockito.when(mockedList.add(Mockito.anyString())).thenReturn(true);

		// Verify behavior
		Assert.assertEquals(true, mockedList.add("gpcoder.com"));
		Assert.assertEquals(true, mockedList.add("mockito"));
		Assert.assertEquals("gpcoder.com", mockedList.get(0));
		Assert.assertEquals("gpcoder.com", mockedList.get(4));
	}
}

Ví dụ Custom Argument Matcher


@Data
@AllArgsConstructor
class Message {
	private String to;
	private String content;
}

class MessageMatcher implements ArgumentMatcher<Message> {

	private Message message;

	public MessageMatcher(Message message) {
		this.message = message;
	}

	// Informs if this matcher accepts the given argument. The method should never
	// assert if the argument doesn't match. Itshould only return false.
	@Override
	public boolean matches(Message message) {
		if (this.message.equals(message)) {
			return true;
		}
		return false;
	}
}

public class ArgumentMatcherTest {

	@Test
	public void customArgumentMatcherTest() {
		List<Message> mockedList = Mockito.mock(List.class);

		Message message = new Message("gpcoder.com", "Custom Argument Matcher");

		// Configure mock to return a specific value on a method call
		Mockito.when(mockedList.add(Mockito.argThat(new MessageMatcher(message)))).thenReturn(true);

		// Verify behavior
		Assert.assertEquals(true, mockedList.add(message));
	}

}

So sánh Custom Argument Matcher vs ArgumentCaptor:

  • ArgumentCaptor : được sử dụng để verify các giá trị của argument.
  • Custom Argument Matcher : được sử dụng cho các argument stub.

Tài liệu tham khảo:

  • https://static.javadoc.io/org.mockito/mockito-core/2.24.5/org/mockito/Mockito.html
5.0
06
Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé! Và Donate tác giả

Shares

Chuyên mục: Unit Test Được gắn thẻ: JUnit, Mockito

Mockito – Annotations
Mockito – Verifying Behavior

Có thể bạn muốn xem:

  • Tổng hợp các bài viết về Unit Test trong Java (15/04/2019)
  • Giới thiệu Mockito (26/03/2019)
  • Unit Testing trong phát triển phần mềm hiện đại (03/03/2019)
  • Làm thế nào để lắng nghe các sự kiện mỗi khi một test được thực thi trong JUnit? (23/03/2019)
  • Làm sao test một Abstract Class trong Java? (12/04/2019)

Bình luận

bình luận

Tìm kiếm

Bài viết mới

  • Clean code 13/01/2024
  • Giới thiệu CloudAMQP – Một RabbitMQ server trên Cloud 02/10/2020
  • Kết nối RabbitMQ sử dụng Web STOMP Plugin 19/06/2020
  • Sử dụng publisher confirm trong RabbitMQ 16/06/2020
  • Sử dụng Dead Letter Exchange trong RabbitMQ 13/06/2020

Xem nhiều

  • Hướng dẫn Java Design Pattern – Factory Method (98059 lượt xem)
  • Hướng dẫn Java Design Pattern – Singleton (97700 lượt xem)
  • Giới thiệu Design Patterns (87764 lượt xem)
  • Lập trình đa luồng trong Java (Java Multi-threading) (86436 lượt xem)
  • Giới thiệu về Stream API trong Java 8 (83839 lượt xem)

Nội dung bài viết

  • 1 Stubbing Methods – when().thenXxx()
  • 2 Stubbing Methods – doXxx().when()
  • 3 Argument matchers

Lưu trữ

Thẻ đánh dấu

Annotation Authentication Basic Java Behavior Pattern Collection Creational Design Pattern Cấu trúc điều khiển Database Dependency Injection Design pattern Eclipse Exception Executor Service Google Guice Gson Hibernate How to Interceptor IO Jackson Java 8 Java Core JDBC JDK Jersey JMS JPA json JUnit JWT Message Queue Mockito Multithreading OOP PowerMockito RabbitMQ Reflection Report REST SOAP Structuaral Pattern Swagger Thread Pool Unit Test Webservice

Liên kết

  • Clean Code
  • JavaTpoint
  • Refactoring Guru
  • Source Making
  • TutorialsPoint
  • W3Schools Online Web Tutorials

Giới thiệu

GP Coder là trang web cá nhân, được thành lập với mục đích lưu trữ, chia sẽ kiến thức đã học và làm việc của tôi. Các bài viết trên trang này chủ yếu về ngôn ngữ Java và các công nghệ có liên quan đến Java như: Spring, JSF, Web Services, Unit Test, Hibernate, SQL, ...
Hi vọng góp được chút ít công sức cho sự phát triển cộng đồng Coder Việt.

Donate tác giả

Tìm kiếm các bài viết của GP Coder với Google Search

Liên hệ

Các bạn có thể liên hệ với tôi thông qua:
  • Trang liên hệ
  • Linkedin: gpcoder
  • Email: contact@gpcoder.com
  • Skype: ptgiang56it

Follow me

Copyright 2025 © GP Coder · All Rights Reserved · Giới thiệu · Chính sách · Điều khoản · Liên hệ ·

Share

Blogger
Delicious
Digg
Email
Facebook
Facebook messenger
Flipboard
Google
Hacker News
Line
LinkedIn
Mastodon
Mix
Odnoklassniki
PDF
Pinterest
Pocket
Print
Reddit
Renren
Short link
SMS
Skype
Telegram
Tumblr
Twitter
VKontakte
wechat
Weibo
WhatsApp
X
Xing
Yahoo! Mail

Copy short link

Copy link