当前位置: 首页 > 工具软件 > Mockito > 使用案例 >

mockito验证参数_Mockito验证

申炳
2023-12-01

mockito验证参数

本文是我们名为“ 用Mockito测试 ”的学院课程的一部分。

在本课程中,您将深入了解Mockito的魔力。 您将了解有关“模拟”,“间谍”和“部分模拟”的信息,以及它们相应的存根行为。 您还将看到使用测试双打和对象匹配器进行验证的过程。 最后,讨论了使用Mockito的测试驱动开发(TDD),以了解该库如何适合TDD的概念。 在这里查看

1.什么是验证?

验证是确认模拟行为的过程。 这对于确定我们正在测试的类是否已按预期方式与其任何依赖项进行交互非常有用。 有时我们对从Mock返回的值不感兴趣,但是对被测类如何与之交互,发送什么值或调用它的频率感兴趣。 确认此行为的过程是验证,Mockito提供了许多工具来允许我们执行此操作。

2.使用verify()

Mockito工具箱中用于执行验证的主要工具是org.mockito.Mockito.verify()方法。 verify方法将Mock对象作为参数,并返回与Mock相同的Class的实例,从而允许您调用Class的方法,Mockito将该Class的方法解释为验证与该方法是否存在某种交互的请求。

让我们再次看一下上一教程中的打印机界面。

public interface Printer {
	
	void printTestPage();

}

我们可以创建一个简单的单元测试来演示使用模拟打印机进行验证

import static org.mockito.Mockito.verify;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class PrinterTest {

	@Mock
	private Printer printer;

	@Test
	public void simple_interaction_verification() {
		// Given
		
		// When
		printer.printTestPage();
		
		// Then
		verify(printer).printTestPage();		
	}
}

我们可以看到我们的单元测试首先调用printer.printTestPage() 。 这是在模拟被测类中的可能交互,但是为了简单起见,我们在单元测试类中进行了模拟。 下一个调用是对verify(printer).printTestPage()的调用。 这指示Mockito检查是否对Mock打印机的printTestPage()方法进行了一次调用。

请仔细注意调用的语法, verify()的参数是Mock对象,而不是方法调用。 如果我们放置了verify(printer.printTestPage())我们将产生一个编译错误。 将此与存根中给定的/ when语法相对应,语法形式为when(mockObject.someMethod()).thenReturn(...)

如果我们没有在此调用上方调用printTestPage()来验证Mockito会生成验证错误,通知我们没有调用printTestPage() ,则该代码如下所示:

Wanted but not invoked:
printer.printTestPage();
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification(PrinterTest.java:24)
Actually, there were zero interactions with this mock.

另外,如果我们再次调用printTestPage() Mockito会产生一个验证错误,通知我们printTestPage()调用过多。 该错误如下所示:

org.mockito.exceptions.verification.TooManyActualInvocations: 
printer.printTestPage();
Wanted 1 time:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification(PrinterTest.java:25)
But was 2 times. Undesired invocation:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification(PrinterTest.java:22)

有用的是,错误告诉我们哪一行代码包含了多余的调用-在本例中为PrinterTest.java第22行。

但是,如果我们想与我们的Mock进行多次交互,该怎么办? Mockito支持吗? 毫无疑问,答案是肯定的!

verify()方法采用org.mockito.verification.VerificationMode类型的第二个参数,该参数可用于提供有关与模拟的所需交互的其他详细信息。

使用内置的验证模式

像往常一样,Mockito在org.mockito.Mockito中提供了许多方便的静态方法来创建VerificationModes,例如:

times(int)
这将验证该方法被调用次数。

@Test
public void simple_interaction_verification_times_1() {
	// Given
		
	// When
	printer.printTestPage();
		
	// Then
	verify(printer, times(1)).printTestPage();		
}

请注意, verify(mock)verify(mock, times(1))的别名。

当然,我们可以使用times()验证多个交互

@Test
public void simple_interaction_verification_times_3() {
	// Given
		
	// When
	printer.printTestPage();
	printer.printTestPage();
	printer.printTestPage();
		
	// Then
	verify(printer, times(3)).printTestPage();		
}

当实际的调用次数与预期的次数不匹配时,此VerificationMode将产生有用的错误。

调用不足:

org.mockito.exceptions.verification.TooLittleActualInvocations: 
printer.printTestPage();
Wanted 3 times:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_times_3(PrinterTest.java:49)
But was 2 times:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_times_3(PrinterTest.java:45)

调用过多:

org.mockito.exceptions.verification.TooManyActualInvocations: 
printer.printTestPage();
Wanted 3 times:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_times_3(PrinterTest.java:50)
But was 4 times. Undesired invocation:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_times_3(PrinterTest.java:47)

atLeastOnce()atLeast(int)
这将验证该方法至少被调用了给定的次数。

@Test
public void simple_interaction_verification_atleastonce() {
	// Given
		
	// When
	printer.printTestPage();
	printer.printTestPage();
		
	// Then
	verify(printer, atLeastOnce()).printTestPage();		
}
@Test
public void simple_interaction_verification_atleast_2() {
	// Given
		
	// When
	printer.printTestPage();
	printer.printTestPage();
	printer.printTestPage();
		
	// Then
	verify(printer, atLeast(2)).printTestPage();		
}

和往常一样,我们得到全面的错误报告:

org.mockito.exceptions.verification.TooLittleActualInvocations: 
printer.printTestPage();
Wanted *at least* 2 times:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_atleast_2(PrinterTest.java:76)
But was 1 time:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_atleast_2(PrinterTest.java:71)

atMost(int)
这将验证该方法最多被调用了给定的次数。

@Test
public void simple_interaction_verification_atmost_3() {
	// Given
		
	// When
	printer.printTestPage();
	printer.printTestPage();
		
	// Then
	verify(printer, atMost(3)).printTestPage();		
}

和错误情况:

org.mockito.exceptions.base.MockitoAssertionError: 
Wanted at most 3 times but was 4
	at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_atmost_3(PrinterTest.java:91)

never()
这将验证未调用该方法。

@Test
public void simple_interaction_verification_never() {
	// Given
		
	// When
		
	// Then
	verify(printer, never()).printTestPage();		
}

和错误情况:

org.mockito.exceptions.verification.NeverWantedButInvoked: 
printer.printTestPage();
Never wanted here:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_never(PrinterTest.java:102)
But invoked here:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_never(PrinterTest.java:98)

only()
这将验证被验证的方法是调用的唯一方法。

@Test
public void simple_interaction_verification_only() {
	// Given
		
	// When
	printer.printTestPage();
		
	// Then
	verify(printer, only()).printTestPage();		
}

通过将以下方法添加到打印机界面,我们可能会产生错误:

void turnOff();

并在我们的测试中调用它

@Test
public void simple_interaction_verification_only_fails() {
	// Given
		
	// When
	printer.printTestPage();
	printer.turnOff();
		
	// Then
	verify(printer, only()).printTestPage();		
}

给出以下错误:

org.mockito.exceptions.verification.NoInteractionsWanted: 
No interactions wanted here:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_only_fails(PrinterTest.java:124)
But found this interaction:
-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_only_fails(PrinterTest.java:120)
***
For your reference, here is the list of all invocations ([?] - means unverified).
1. [?]-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_only_fails(PrinterTest.java:120)
2. [?]-> at com.javacodegeeks.hughwphamill.mockito.stubbing.PrinterTest.simple_interaction_verification_only_fails(PrinterTest.java:121)

创建自定义验证模式

您可以通过实现org.mockito.verification.VerificationMode接口来创建自己的自定义验证模式。 请注意,这使用的某些类不构成Mockito的公共API的一部分。 在编写本文时,已经计划将它们提升为公共API,但是在实现发生更改之前,应谨慎使用此功能。

VerificationMode公开了一个void verify(VerificationData data)方法,该方法用于验证我们感兴趣的模拟调用是否正确发生。

您可以使用几个内部Mockito类在VerificationMode中为您提供帮助:

  • InvocationsFinder将返回带有感兴趣的模拟的所有调用的列表。
  • InvocationMarker可用于将模拟调用标记为已验证。
  • Reporter公开了许多引发各种VerificationFailure错误的快捷方式。
  • InvocationMatcherInvocationsMarker结合使用以查找所需的调用(如果发生)。

我们将创建一个称为FirstVerificationMode ,它将验证给定方法是否是Mock上的首次调用。 我们将创建一个实现VerificationMode的类,并在verify方法中找到所有匹配的调用并验证两件事:

1.我们想要的调用实际上发生了,如果没有发生,我们将使用Reporter引发“所需但未调用”的错误。
2.我们想要的调用是Mock上的第一次调用,如果不是,我们将抛出一个新的异常,并带有适当的消息,详细说明预期的调用和实际的调用。

最后,我们将通过静态工厂方法公开First的创建,以与Mockito语法保持一致。

First类如下所示:

package com.javacodegeeks.hughwphamill.mockito.verification;

import java.util.Arrays;
import java.util.List;

import org.mockito.exceptions.Reporter;
import org.mockito.exceptions.verification.VerificationInOrderFailure;
import org.mockito.internal.debugging.LocationImpl;
import org.mockito.internal.invocation.InvocationMarker;
import org.mockito.internal.invocation.InvocationMatcher;
import org.mockito.internal.invocation.InvocationsFinder;
import org.mockito.internal.verification.api.VerificationData;
import org.mockito.invocation.Invocation;
import org.mockito.verification.VerificationMode;

public class First implements VerificationMode {
	
	private final InvocationsFinder finder = new InvocationsFinder();
	private final InvocationMarker marker = new InvocationMarker();
	private final Reporter reporter = new Reporter();
	
	public static VerificationMode first() {
		return new First();
	}

	@Override
	public void verify(VerificationData data) {
		List<Invocation> invocations = data.getAllInvocations();
		InvocationMatcher matcher = data.getWanted();
		
		List<Invocation> chunk = finder.findInvocations(invocations, matcher);
		
		if (invocations.size() == 0 || chunk.size() == 0) {
			reporter.wantedButNotInvoked(matcher);
		} else if (!sameInvocation(invocations.get(0), chunk.get(0))) {			
			reportNotFirst(chunk.get(0), invocations.get(0));
		}
		
		marker.markVerified(chunk.get(0), matcher);	
	}

	private boolean sameInvocation(Invocation left, Invocation right) {
		if (left == right) {
			return true;			
		}	
		return left.getMock().equals(right.getMock()) && left.getMethod().equals(right.getMethod()) && Arrays.equals(left.getArguments(), right.getArguments());
	}
	
	private void reportNotFirst(Invocation wanted, Invocation unwanted) {
		StringBuilder message = new StringBuilder();
		message.append("\\nWanted first:\\n").append(wanted).append("\\n").append(new LocationImpl());
		message.append("\\nInstead got:\\n").append(unwanted).append("\\n").append(unwanted.getLocation()).append("\\n");
		throw new VerificationInOrderFailure(message.toString());
		
	}
}

我们可以在这样的测试案例中使用它:

@Test
public void simple_interaction_verification_first() {
	// Given
		
	// When
	printer.printTestPage();
	printer.turnOff();		
				
	// Then
	verify(printer, first()).printTestPage();		
}

或捕获一些意外行为:

@Test
public void simple_interaction_verification_first_fails() {
	// Given
		
	// When
	printer.turnOff();
	printer.printTestPage();				
				
	// Then
	verify(printer, first()).printTestPage();		
}

会产生以下错误:

org.mockito.exceptions.verification.VerificationInOrderFailure: 
Wanted first:
printer.printTestPage();
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.simple_interaction_verification_first_fails(PrinterTest.java:152)
Instead got:
printer.turnOff();
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.simple_interaction_verification_first_fails(PrinterTest.java:148)

参数验证

我们将检查带有参数的方法的验证,因此让我们更新Printer接口以添加新方法。 此方法将模拟打印文本字符串,并将包含以下参数:

  • 字符串文本–要打印的文本。
  • 整数副本–要制作的副本数量。
  • 布尔整理–整理副本为True。
public interface Printer {
	
	void printTestPage();
	
	void turnOff();
	
	void print(String text, Integer copies, Boolean collate);

}

带参数的验证使我们不仅可以验证与Mock的交互,还可以验证将哪些参数传递给了Mock。 要使用参数执行验证,您只需在Mock上的verify调用中将感兴趣的参数传递到Mocked方法中即可。

@Test
public void verificatin_with_actual_parameters() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	Integer copies = 3;
	Boolean collate = true;
		
	// When
	printer.print(text, copies, collate);
				
	// Then
	verify(printer).print(text, copies, collate);		
}

再次仔细注意verify()的语法,我们在对verify方法返回的对象上调用print() ,而不是直接在Mock上。 您可以看到,只需将值传递给print()就足以使用参数执行验证。

以下测试将失败:

@Test
public void verificatin_with_actual_parameters_fails() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	String text2 = "Ut enim ad minim veniam, quis nostrud exercitation ullamco " 
			+ "laboris nisi ut aliquip ex ea commodo consequat.";
	Integer copies = 3;
	Boolean collate = true;
		
	// When
	printer.print(text2, copies, collate);
				
	// Then
	verify(printer).print(text, copies, collate);		
}

具有以下输出:

Argument(s) are different! Wanted:
printer.print(
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
    3,
    true
);
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verificatin_with_actual_parameters_fails(PrinterTest.java:185)
Actual invocation has different arguments:
printer.print(
    "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
    3,
    true
);

您可以看到Mockito在错误跟踪中为您提供了预期的参数和实际的参数,从而非常容易调试失败的测试。

与简单验证一样,我们可以在使用Parameters时使用VerificationMode进行更具体的验证。 关键区别在于,我们指定的VerificationMode仅适用于具有指定参数的调用。 因此,例如,如果我们使用Never never()的验证模式,则说明该方法永远不会使用指定的参数调用,而不是根本不会调用。

通过以下测试是因为即使调用了print()方法,也绝不会使用指定的参数来调用它。

@Test
public void verification_with_actual_parameters_and_verification_mode() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	String text2 = "Ut enim ad minim veniam, quis nostrud exercitation ullamco " 
			+ "laboris nisi ut aliquip ex ea commodo consequat.";
	Integer copies = 3;
	Boolean collate = true;
		
	// When
	printer.print(text, copies, collate);
				
	// Then
	verify(printer, never()).print(text2, copies, collate);		
}

当我们对我们的多次调用Mock ,我们可以验证每一个单独使用多次调用verify

@Test
public void multiple_verification_with_actual_parameters() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	String text2 = "Ut enim ad minim veniam, quis nostrud exercitation ullamco " 
			+ "laboris nisi ut aliquip ex ea commodo consequat.";
	Integer copies = 3;
	Boolean collate = true;
		
	// When
	printer.print(text, copies, collate);
	printer.print(text2, copies, collate);
				
	// Then
	verify(printer).print(text, copies, collate);
	verify(printer).print(text2, copies, collate);		
}

在很多情况下,我们对交互的实际参数不感兴趣或不知道是什么,在这种情况下,就像在存根阶段,我们可以使用参数匹配器来验证交互。

看下面的测试

@Test
public void verification_with_matchers() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	String text2 = "Ut enim ad minim veniam, quis nostrud exercitation ullamco " 
			+ "laboris nisi ut aliquip ex ea commodo consequat.";
	Integer copies3 = 3;
	Integer copies4 = 4;
	Boolean doCollate = true;
	Boolean doNotCollate = false;
		
	// When
	printer.print(text, copies3, doCollate);
	printer.print(text2, copies4, doNotCollate);
				
	// Then
	verify(printer, times(2)).print(anyString(), anyInt(), anyBoolean());		
}

请注意,我们两次调用printer.print() ,每次都使用完全不同的参数。 我们使用参数匹配器在最后一行验证与模拟的两种交互。 请记住, verify(printer).print()隐式意味着我们要验证与Mock上的print()方法的一个且只有一个交互,因此我们必须包括times(2) VerificationMode以确保我们验证与嘲笑。

验证中使用的参数匹配器与存根阶段中使用的参数匹配器相同。 请重新访问“ 存根”教程 ,以获取更多可用匹配器列表。

与存根阶段一样,我们不能将实参与实参混合并匹配,但是如果我们不在乎传递到打印机进行打印的文本,该怎么办呢?我们只想验证应该有5份整理后的副本?

在这种情况下,与Stubbing一样,我们可以使用eq()匹配器来验证我们感兴趣的实数值,而对文本使用anyString()

@Test
public void verification_with_mixed_matchers() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	String text2 = "Ut enim ad minim veniam, quis nostrud exercitation ullamco " 
			+ "laboris nisi ut aliquip ex ea commodo consequat.";
	Integer copies = 5;
	Boolean collate = true;
		
	// When
	printer.print(text, copies, collate);
	printer.print(text2, copies, collate);
				
	// Then
	verify(printer, times(2)).print(anyString(), eq(copies), eq(collate));		
}

通过,而以下测试将失败

@Test
public void verification_with_mixed_matchers_fails() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	Integer copies5 = 5;
	Integer copies10 = 10;
	Boolean collate = true;
		
	// When
	printer.print(text, copies10, collate);
				
	// Then
	verify(printer).print(anyString(), eq(copies5), eq(collate));		
}

超时验证

有时,当测试多线程应用程序时,我们希望确保某些模拟交互在给定的超时时间内发生。 Mockito提供了一个timeout()方法来帮助我们实现这一目标。

请注意,虽然此功能可用,但Mockito文档警告不要使用它:

“感觉该功能应该很少使用-找出测试多线程系统的更好方法。”

因此,在避免健康警告的情况下,让我们看几个示例。

假设我们有一些线程将要执行printTestPage()并且我们想验证这种情况是否在100毫秒内发生。 我们可以使用timeout(100)来实现。 可以将其作为第二个参数传递给verify() ,并返回一个VerificationWithTimout,它是VerificationMode的扩展。

以下测试演示其用法:

@Test
public void verification_with_timeout() {
	// Given
		
	// When
	Executors.newFixedThreadPool(1).execute(() -> printer.printTestPage());
				
	// Then
	verify(printer, timeout(100)).printTestPage();
}

在这里,我们使用Executor创建一个新的ExecutorService ,它可以执行Runnables。 我们利用Java 8 Lambda表达式即时构建一个新的Runnable ,它将执行对printTestPage()的调用。 然后,我们调用verify()传递100ms的超时时间。

我们现在可以看看失败的测试。 这次,我们将使用方法引用来生成Runnable,这是因为Runnable的主体更加复杂-它引入了200ms的睡眠。

@Test
public void verification_with_timeout_fails() throws InterruptedException {
	// Given
		
	// When
	Executors.newFixedThreadPool(1).execute(this::printTestWithSleep);
			
	// Then
	verify(printer, timeout(100)).printTestPage();
}
	
private void printTestWithSleep() {
	try {
		Thread.sleep(200L);
		printer.printTestPage();
	} catch (InterruptedException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

测试失败,并显示一条简单的“需要但未调用”消息。

还可以使用返回的VerificationWithTimeout公开的方法将VerificationModes添加到timeout()

@Test
public void verification_with_timeout_with_verification_mode() {
	// Given
	int poolsize = 5;
	
	// When
	ExecutorService service = Executors.newFixedThreadPool(poolsize);
	service.execute(this::printTestWithSleep);
	service.execute(this::printTestWithSleep);
	service.execute(this::printTestWithSleep);
				
	// Then
	verify(printer, timeout(500).times(3)).printTestPage();
}

在这里,我们使用带有睡眠的测试来执行printTestPage() 3次,使用ExecutorService可以运行5个并行线程,以便睡眠同时发生,从而允许所有3次调用都在500ms的限制内发生。

通过将可用线程数减少到1,迫使printTestWithSleep调用顺序执行并超过500ms超时,可以使测试失败。

@Test
public void verification_with_timeout_with_verification_mode_fails() {
	// Given
	int poolsize = 1;
	
	// When
	ExecutorService service = Executors.newFixedThreadPool(poolsize);
	service.execute(this::printTestWithSleep);
	service.execute(this::printTestWithSleep);
	service.execute(this::printTestWithSleep);
				
	// Then
	verify(printer, timeout(500).times(3)).printTestPage();
}

前两个调用发生在400毫秒内,而最后一个调用发生在600毫秒后,导致500毫秒超时失败,并显示以下输出:

org.mockito.exceptions.verification.TooLittleActualInvocations: 
printer.printTestPage();
Wanted 3 times:
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verification_with_timeout_with_verification_mode_fails(PrinterTest.java:410)
But was 2 times:
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.printTestWithSleep(PrinterTest.java:376)

3.验证无交互且无更多交互

我们已经看到可以使用never() VerificationMode来确保不调用Mock的特定方法,但是如何验证Mock上没有任何交互呢?

Mockito为我们提供了verifyZeroInteractions()方法。 此方法使用varargs允许我们验证在一行代码中没有与多个模拟进行任何交互。

让我们在测试类中添加其他一些Mock:

@Mock
private List<String> list;

现在,我们可以编写以下简单测试,以验证与打印机或列表之间没有任何交互

@Test
public void verify_zero_interactions() {
	// Given
		
	// When
				
	// Then
	verifyZeroInteractions(printer, list);		
}

与往常一样,以下测试将失败

@Test
public void verify_zero_interactions_fails() {
	// Given
		
	// When
	printer.printTestPage();
				
	// Then
	verifyZeroInteractions(printer, list);		
}

具有以下输出

org.mockito.exceptions.verification.NoInteractionsWanted: 
No interactions wanted here:
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verify_zero_interactions_fails(PrinterTest.java:288)
But found this interaction:
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verify_zero_interactions_fails(PrinterTest.java:285)
Actually, above is the only interaction with this mock.
	at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verify_zero_interactions_fails(PrinterTest.java:288)

我们还可以使用verifyNoMoreInteractions()方法来验证一旦验证了一定数量的调用,就不再与Mock进行交互。

@Test
public void verify_no_more_interactions() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	Integer copies = 3;
	Boolean collate = true;
		
	// When
	printer.print(text, copies, collate);
				
	// Then
	verify(printer).print(text, copies, collate);
	verifyNoMoreInteractions(printer);
}

您可以在上面看到我们验证了对print()的调用,然后验证了与Mock的交互作用。

以下测试将失败,因为在对print()的验证调用之后,该模拟还存在其他交互

@Test
public void verify_no_more_interactions_fails() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	Integer copies = 3;
	Boolean collate = true;
		
	// When
	printer.print(text, copies, collate);
	printer.turnOff();
				
	// Then
	verify(printer).print(text, copies, collate);
	verifyNoMoreInteractions(printer);
}

测试失败会生成以下消息:

org.mockito.exceptions.verification.NoInteractionsWanted: 
No interactions wanted here:
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verify_no_more_interactions_fails(PrinterTest.java:342)
But found this interaction:
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verify_no_more_interactions_fails(PrinterTest.java:338)
***
For your reference, here is the list of all invocations ([?] - means unverified).
1. -> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verify_no_more_interactions_fails(PrinterTest.java:337)
2. [?]-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verify_no_more_interactions_fails(PrinterTest.java:338)

4.按顺序验证

有时,我们想验证与Mocks的交互是否以特定顺序发生。 Mockito提供了一个名为InOrder的类来帮助我们实现这一目标。

我们要做的第一件事是向InOrder对象注册要确认调用顺序的模拟程序。 然后,我们在Mock对象上执行方法,然后为每个要验证其执行顺序的模拟方法调用InOrder对象的verify()方法(以我们要验证它们发生的顺序)。

InOrder.verify()方法的行为几乎与标准的verify()方法类似,允许您传入VerificationModes,但是无法使用InOrder进行带有超时的验证。

这是按顺序进行验证的示例:

@Test
public void verify_in_order() {
	// Given
	InOrder inOrder = Mockito.inOrder(printer);
		
	// When
	printer.printTestPage();
	printer.turnOff();
				
	// Then
	inOrder.verify(printer).printTestPage();
	inOrder.verify(printer).turnOff();
}

相反的失败测试:

@Test
public void verify_in_order_fails() {
	// Given
	InOrder inOrder = Mockito.inOrder(printer);
		
	// When
	printer.turnOff();
	printer.printTestPage();
				
	// Then
	inOrder.verify(printer).printTestPage();
	inOrder.verify(printer).turnOff();
}

失败并显示以下错误消息:

org.mockito.exceptions.verification.VerificationInOrderFailure: 
Verification in order failure
Wanted but not invoked:
printer.turnOff();
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verify_in_order_fails(PrinterTest.java:440)
Wanted anywhere AFTER following interaction:
printer.printTestPage();
-> at com.javacodegeeks.hughwphamill.mockito.verification.PrinterTest.verify_in_order_fails(PrinterTest.java:436)

您还可以在多个模拟中按顺序验证:

@Test
public void verify_in_order_multiple() {
	// Given
	InOrder inOrder = Mockito.inOrder(printer, list);
		
	// When
	printer.printTestPage();
	list.clear();
	printer.turnOff();
				
	// Then
	inOrder.verify(printer).printTestPage();
	inOrder.verify(list).clear();
	inOrder.verify(printer).turnOff();
}

5.争论者

我们已经研究过使用参数匹配器来验证带有特定参数的调用,但是Mockito让我们走得更远,捕获传递给调用的参数并直接对它们执行断言。 这对于验证将在传递给协作者的对象上执行的类中的登录非常有用。 执行此操作的工具是一个称为ArgumentCaptor的类和一个名为@Captor的注释。

让我们在模型中创建一个名为PrinterDiagnostics的新类。 它将包含一个Printer并公开一个名为diagnosticPrint的方法,该方法将具有与Printer.print()相同的参数,并将一些诊断信息添加到要打印的文本中。

package com.javacodegeeks.hughwphamill.mockito.verification;

public class PrinterDiagnostics {
	
	private Printer printer;
	
	public PrinterDiagnostics(Printer printer) {
		this.printer = printer;
	}
	
	public void diagnosticPrint(String text, Integer copies, Boolean collate) {
		StringBuilder diagnostic = new StringBuilder();
		diagnostic.append("** Diagnostic Print **\\n");
		diagnostic.append("*** Copies: ").append(copies).append(" ***\\n");
		diagnostic.append("*** Collate: ").append(collate).append(" ***\\n");
		diagnostic.append("********************\\n\\n");
		
		printer.print(new StringBuilder().append(diagnostic).append(text).toString(), copies, collate);
	}
}

我们将使用模拟PrinterArgumentCaptor创建一个新的JUnit测试来测试该类,我们将使用它们来验证对打印机的输入。

这是JUnit测试的框架:

package com.javacodegeeks.hughwphamill.mockito.verification;

import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class PrinterDiagnosticsTest {
	
	private PrinterDiagnostics diagnostics;
	@Mock
	private Printer printer;
	@Captor
	private ArgumentCaptor<String> textCaptor;

	@Before
	public void setUp() throws Exception {
		diagnostics = new PrinterDiagnostics(printer);
	}
}

在这里,我们看到我们创建了一个被测类的实例,诊断,一个代表打印机,打印机的Mock以及一个String参数的ArgumentCaptor,以捕获输入到打印机的称为textCaptor的文本。 您可以看到我们用@Captor批注为ArgumentCaptor批注。 因为我们使用了注释,所以Mockito将自动为我们实例化ArgumentCaptor。

您还可以看到ArgumentCaptor是泛型类型,在这种情况下,我们将使用Type Argument String创建一个ArgumentCaptor,因为我们将捕获文本参数(即String)。 如果要捕获collat​​e参数,则可能已经创建了ArgumentCaptor collateCaptor ArgumentCaptor collateCaptor

在我们的@Before方法中,我们只需创建一个新的PrinterDiagnostics 。,即可通过其构造函数注入模拟打印机。

现在让我们创建测试。 我们要确保两件事:

1.份数添加到输入文本中。
2. collat​​e参数的状态已添加到输入文本。
3.保留原始文本。

我们可能还想验证现实世界中的格式和星号,但现在让我们满足于验证上述两个条件。

在测试中,我们将初始化测试数据,执行对diagnosticPrint()的调用,然后将verify()与ArgumentCaptor的capture()方法结合使用以捕获文本参数。 然后,我们将对捕获的String进行必要的断言,以通过使用getValue()方法检索捕获的文本来验证我们期望的行为。

@Test
public void verify_diagnostic_information_added_to_text() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	Integer copies = 3;
	Boolean collate = true;
	String expectedCopies = "Copies: " + copies;
	String expectedCollate = "Collate: " + collate;
		
	// When
	diagnostics.diagnosticPrint(text, copies, collate);
		
	// Then
	verify(printer).print(textCaptor.capture(), eq(copies), eq(collate));
	assertTrue(textCaptor.getValue().contains(expectedCopies));
	assertTrue(textCaptor.getValue().contains(expectedCollate));
	assertTrue(textCaptor.getValue().contains(text));	
}

请注意, capture()行为有点类似于Matcher,因为您必须对其他参数使用Matchers。 我们使用eq()匹配器来确保我们通过预期的副本并整理参数。

如果对嘲笑的方法进行了多次调用,我们可以使用ArgumentCaptorgetValues()方法获取所有字符串的列表,这些字符串作为每次调用中的text参数传递。

让我们在PrinterDiagnostics中创建一个新方法,该方法将对单个整理的副本以及原始打印进行诊断打印:

public void diagnosticAndOriginalPrint(String text, Integer copies, Boolean collate) {
	diagnosticPrint(text, copies, collate);
	printer.print(text, copies, collate);
}

现在,我们可以使用以下测试方法进行测试:

@Test
public void verify_diagnostic_information_added_to_text_and_original_print() {
	// Given
	String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
			+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
	Integer copies = 3;
	Boolean collate = true;
	String expectedCopies = "Copies: " + copies;
	String expectedCollate = "Collate: " + collate;
		
	// When
	diagnostics.diagnosticAndOriginalPrint(text, copies, collate);
		
	// Then
	verify(printer, times(2)).print(textCaptor.capture(), eq(copies), eq(collate));
	List<String> texts = textCaptor.getAllValues();
	assertEquals(2, texts.size());
		
	// First captured text is Diagnostic Print
	assertTrue(texts.get(0).contains(expectedCopies));
	assertTrue(texts.get(0).contains(expectedCollate));
	assertTrue(texts.get(0).contains(text));
		
	// Second captured text is normal Print
	assertFalse(texts.get(1).contains(expectedCopies));
	assertFalse(texts.get(1).contains(expectedCollate));
	assertEquals(text, texts.get(1));
}

请注意,我们必须在验证中使用times(2) ,因为我们希望两次调用print()方法。

当我们的参数是复杂对象或由测试代码创建时,ArgumentCaptors特别有用。 您可以轻松捕获参数,并对其进行所需的任何类型的验证。

六,结论

我们已经详细研究了Mockito的验证阶段。 我们已经研究了开箱即用地验证行为,创建自己的验证模式以及使用Argument Captors对数据执行更复杂的断言的方法。

在下一个教程中,我们将研究Hamcrest Matcher库如何使我们进一步进行测试验证,从而使我们能够进行非常精细的行为验证。

7.下载源代码

这是关于Mockito验证的课程。 您可以在此处下载源代码: mockito3-verification

翻译自: https://www.javacodegeeks.com/2015/11/mockito-verification.html

mockito验证参数

 类似资料: