单元测试实践篇:Mock

本文详细介绍了如何在Java中使用Mockito和PowerMock进行单元测试,包括mock静态方法、私有方法、final方法,以及处理enum、构造函数和使用AssertJ进行断言。它展示了如何创建mock对象,设置行为,验证方法调用,并演示了PowerMock的更强大功能如mock构造器和内部状态的修改。
摘要由CSDN通过智能技术生成

verify(mock, timeout(timeout + delta).times(2)).callMethod(“test”);

// immediately success

verify(mock, timeout(10)).sayHello();

async.get();

// after() awaits full duration to check if verification passes

verify(mock, after(10).times(2)).callMethod(“test”);

verify(mock, after(10)).sayHello();

}

spy

spy 的官方定义是:

partial mocking, real methods are invoked but still can be verified and stubbed

会调用被 spy 的真实对象的方法,但仍能被 Mockiton 所直接用于 mock 和 verify,也就是说在没有配置 mock 行为的情况下默认是调用被 mock 对象的真实方法。

  • 句式 doXxx…when 当同一目标方法上定义了多个 mock 行为,后序 mock 可以覆盖前序 mock

  • clearInvocations 仅清理之前的调用

  • reset 会重置为初始状态(所有中途的赋值都会被清理掉)

@Test

public void testDoReturn() {

// real creation

List list = new LinkedList();

List spy = spy(list);

//optionally, you can stub out some methods:

int mockSize = 100;

when(spy.size()).thenReturn(mockSize);

//size() method was stubbed - 100 is printed

assertEquals(spy.size(), mockSize);

// Overriding a previous exception-stubbing:

when(spy.size()).thenThrow(new IllegalStateException(“not init”));

doReturn(mockSize).when(spy).size();

assertEquals(spy.size(), mockSize);

//Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)

Assertions.assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> spy.get(0));

doReturn(“mock data”).when(spy).get(1);

//using the spy calls real methods

spy.add(“one”);

assertEquals(spy.get(0), “one”);

/*

Use this method in order to only clear invocations, when stubbing is non-trivial. Use-cases can be:

You are using a dependency injection framework to inject your mocks.

The mock is used in a stateful scenario. For example a class is Singleton which depends on your mock.

Try to avoid this method at all costs. Only clear invocations if you are unable to efficiently test your program.

*/

clearInvocations(spy);

verify(spy, times(0)).add(“two”);

reset(spy);

when(spy.size()).thenReturn(0);

assertEquals(spy.size(), 0);

}

 PowerMock

以上介绍的是 Mockiton 中常用的API,而 PowerMock 则更强大,可以 mock static 方法,private 方法,final 方法,enum,构造函数调用等。

示例代码中用到的测试类如下:

public enum TypeEnum {

Y(“TRUE”),

N(“FALSE”);

private final String title;

TypeEnum(String title) {

this.title = title;

}

public String getTitle() {

return title;

}

}

public final class FinalTarget {

public FinalTarget() { }

public final String finalMethod() {

return “Hello final!”;

}

}

public class StaticTarget {

public static String firstMethod(String name) {

return “Hello " + name + " !”;

}

public static String secondMethod() {

return “Hello no one!”;

}

}

public class PartialTarget {

private String arg;

public PartialTarget(String arg) {

this.arg = arg;

}

public PartialTarget() { }

public String getArg() {

return arg;

}

private String privateWithArg(String arg) {

return "Hello privateWithArg! " + arg;

}

public String privateMethodCaller(String arg) {

return privateWithArg(arg) + " privateMethodCall.";

}

}

类注解

在使用 PowerMockito mock static , private , final , enum , constructor 之前需要在测试类上加入如下注解:

@RunWith(PowerMockRunner.class)

@PrepareForTest({StaticTarget.class, PartialTarget.class, TypeEnum.class, FinalTarget.class})

static

PowerMockito.mockStatic 声明了要 mock static 方法的类

PowerMockito.mockStatic(StaticTarget.class);

StaticTarget.firstMethod(“xxx”);

verify

值得注意的是,它的 verify 方法使用比 Mockiton 更复杂。

需要先声明一下验证目标类的静态方法再紧接着调用一下,表示待验证的目标方法

PowerMockito.verifyStatic(StaticTarget.class); // 1

StaticTarget.firstMethod(invokeParam); // 2

也有类似于 Mockiton 的调用次数校验:

PowerMockito.verifyStatic(StaticTarget.class, times(1));

PowerMockito.verifyStatic(StaticTarget.class, Mockito.atLeastOnce());

private

PowerMock 模拟 private 方法 “privateWithArg” 的返回值并校验 “privateWithArg” 被调用的次数

PartialTarget partialMock = PowerMockito.mock(PartialTarget.class);

doCallRealMethod().when(partialMock).privateMethodCaller(anyString());

PowerMockito.doReturn(“mockResult”).when(partialMock, “privateWithArg”, any());

// privateMethodCaller will invoke method privateWithArg

String result = partialMock.privateMethodCaller(“arg”);

Assert.assertEquals(result, “mockResult privateMethodCall.”);

PowerMockito.verifyPrivate(partialMock, times(1)).invoke(“privateWithArg”, “arg”);

final

PowerMock 校验 mock final方法

FinalTarget finalTarget = PowerMockito.mock(FinalTarget.class);

String finalReturn = “finalReturn”;

PowerMockito.when(finalTarget.finalMethod()).thenReturn(finalReturn);

Assert.assertThat(finalTarget.finalMethod(), is(finalReturn));

enum

PowerMock mock enum,这里的 Whitebox.setInternalState 可以设置 TypeEnum fieldName=N 的值为给定的 mock 枚举

String mockValue = “mock title”;

TypeEnum typeMock = PowerMockito.mock(TypeEnum.class);

Whitebox.setInternalState(TypeEnum.class, “N”, typeMock);

when(typeMock.getTitle()).thenReturn(mockValue);

Assert.assertEquals(TypeEnum.N.getTitle(), mockValue);

Assert.assertEquals(TypeEnum.Y.getTitle(), “TRUE”);

constructor

构造器 mock 与 verify

String arg = “special arg”;

PartialTarget partialWithArgSpy = PowerMockito.spy(new PartialTarget(arg));

whenNew(PartialTarget.class).withNoArguments().thenReturn(partialWithArgSpy);

PartialTarget partialNoArg = new PartialTarget();

Assert.assertEquals(partialNoArg.getArg(), arg);

verifyNew(PartialTarget.class).withNoArguments();

完整示例如下:

import org.assertj.core.api.Assertions;

import org.junit.Assert;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.mockito.Mockito;

import org.powermock.api.mockito.PowerMockito;

import org.powermock.core.classloader.annotations.PrepareForTest;

import org.powermock.modules.junit4.PowerMockRunner;

import org.powermock.reflect.Whitebox;

import static org.hamcrest.core.Is.is;

import static org.mockito.ArgumentMatchers.anyString;

import static org.mockito.Mockito.times;

import static org.powermock.api.mockito.PowerMockito.doCallRealMethod;

import static org.powermock.api.mockito.PowerMockito.verifyNew;

import static org.powermock.api.mockito.PowerMockito.when;

import static org.powermock.api.mockito.PowerMockito.whenNew;

@RunWith(PowerMockRunner.class)

@PrepareForTest({StaticTarget.class, PartialTarget.class, TypeEnum.class, FinalTarget.class})

public class PowerMockTest {

@Test

public void testStatic() throws Exception {

PowerMockito.mockStatic(StaticTarget.class);

String mockResult = “Static mock”;

PowerMockito.when(StaticTarget.firstMethod(anyString())).thenReturn(mockResult);

String invokeParam = “any String parameter”;

Assert.assertEquals(StaticTarget.firstMethod(invokeParam), mockResult);

// Verification of a static method is done in two steps.

PowerMockito.verifyStatic(StaticTarget.class); // 1

// StaticTarget.secondMethod();// not invoked

StaticTarget.firstMethod(invokeParam);// 2

// use argument matchers

PowerMockito.verifyStatic(StaticTarget.class); // 1

StaticTarget.firstMethod(anyString()); // 2

// atLeastOnce

PowerMockito.verifyStatic(StaticTarget.class, Mockito.atLeastOnce()); // 1

StaticTarget.firstMethod(anyString()); // 2

// times

PowerMockito.verifyStatic(StaticTarget.class, times(1)); // 1

StaticTarget.firstMethod(anyString()); // 2

// partial mocking of a private method & verifyPrivate

// PartialTarget partialNoArgSpy = PowerMockito.spy(new PartialTarget());

PartialTarget partialMock = PowerMockito.mock(PartialTarget.class);

doCallRealMethod().when(partialMock, “privateMethodCaller”, anyString());

PowerMockito.doReturn(“mockResult”).when(partialMock, “privateWithArg”, any());

// privateMethodCaller will invoke method privateWithArg

String result = partialMock.privateMethodCaller(“arg”);

Assert.assertEquals(result, “mockResult privateMethodCall.”);

PowerMockito.verifyPrivate(partialMock, times(1)).invoke(“privateWithArg”, “arg”);

// Final

FinalTarget finalTarget = PowerMockito.mock(FinalTarget.class);

String finalReturn = “finalReturn”;

PowerMockito.when(finalTarget.finalMethod()).thenReturn(finalReturn);

Assert.assertThat(finalTarget.finalMethod(), is(finalReturn));

// enum

String mockValue = “mock title”;

TypeEnum typeMock = PowerMockito.mock(TypeEnum.class);

Whitebox.setInternalState(TypeEnum.class, “N”, typeMock);

when(typeMock.getTitle()).thenReturn(mockValue);

Assert.assertEquals(TypeEnum.N.getTitle(), mockValue);

Assert.assertEquals(TypeEnum.Y.getTitle(), “TRUE”);

// verify New

String arg = “special arg”;

PartialTarget partialWithArgSpy = PowerMockito.spy(new PartialTarget(arg));

whenNew(PartialTarget.class).withNoArguments().thenReturn(partialWithArgSpy);

PartialTarget partialNoArg = new PartialTarget();

Assert.assertEquals(partialNoArg.getArg(), arg);

verifyNew(PartialTarget.class).withNoArguments();

// throw exception

PowerMockito.doThrow(new ArrayStoreException(“Mock secondMethod error”)).when(StaticTarget.class);

StaticTarget.secondMethod();

// AssertJ: Exception assertions

Assertions.assertThatThrownBy(StaticTarget::secondMethod)

.isInstanceOf(ArrayStoreException.class)

.hasNoCause()

.hasMessage(“Mock secondMethod error”);

}

}

 AssertJ

上面提到的 AssertJ 是 Assert 的一些功能增强,以流式编程的方式调用,下面介绍一些常用的用法

  • isIn,isNotIn 和 matches 用于断言匹配条件

  • filteredOn 可以针对 assertThat 中传入的参数进行过滤,类似 java8 中Stream() 的 filter 方法

  • extracting 可以针对 assertThat 中传入的元组进行字段提取校验

  • assertThatExceptionOfType 和 assertThatThrownBy 可用于捕获预期的异常

为了方便使用,AssertJ 还提供了几种常用的异常断言的包装器:

// AssertJ provides wrappers for common exception types

Assertions.assertThatNoException();

Assertions.assertThatIOException();

Assertions.assertThatNullPointerException();

Assertions.assertThatIllegalStateException();

Assertions.assertThatIllegalArgumentException();

示例如下:

import org.assertj.core.api.Assertions;

import org.junit.Test;

import java.util.Arrays;

import java.util.List;

import static org.assertj.core.api.Assertions.tuple;

public class AssertTest {

@Test

public void testAssertJ() {

String title = “foo”;

AssertTarget assertTarget = new AssertTarget(title, 12, TypeEnum.Y);

String msg = “Illegal Argument error”;

Exception cause = new NullPointerException(“cause exception msg”);

Assertions.assertThatExceptionOfType(IllegalArgumentException.class)

.isThrownBy(() -> assertTarget.throwIllegalArgumentException(msg, cause))

.withMessage(msg)

.withMessageContaining(“Argument error”)

.overridingErrorMessage(“new error message”)

.withCause(cause);

Assertions.assertThatThrownBy(() -> assertTarget.throwIllegalArgumentException(msg, cause))

.isInstanceOf(IllegalArgumentException.class)

.hasMessageContaining(“Argument error”);

Assertions.assertThat(assertTarget.getTitle())

// as() is used to describe the test and will be shown before the error message

.as(“PartialTarget’s arg is not match”, assertTarget.getTitle())

.startsWith(title)

.endsWith(title)

.contains(title)

.isNotEqualTo(“foo bar”)

.isEqualToIgnoringCase(“FOO”)

.isEqualTo(title);

AssertTarget target1 = new AssertTarget(“testTitle”, 12, TypeEnum.N);

AssertTarget target2 = new AssertTarget(“titleVal1”, 16, TypeEnum.N);

AssertTarget target3 = new AssertTarget(“titleVal2”, 18, TypeEnum.Y);

AssertTarget target4 = new AssertTarget(“titleVal3”, 20, TypeEnum.N);

List assertTargetRing = Arrays.asList(target1, target2, target3);

Assertions.assertThat(target1.getNum()).withFailMessage(“the num not matches”).isEqualTo(12);

Assertions.assertThat(target1.getType().equals(TypeEnum.N)).isTrue();

Assertions.assertThat(target1).isIn(assertTargetRing);

Assertions.assertThat(target4).isNotIn(assertTargetRing);

Assertions.assertThat(target4).matches(e -> e.getNum() > 18 && e.getType().equals(TypeEnum.N));

Assertions.assertThat(assertTargetRing)

// extracting multiple values at once grouped in tuples

.extracting(“num”, “type.title”)

.contains(tuple(16, TypeEnum.N.getTitle())

, tuple(18, TypeEnum.Y.getTitle()));

Assertions.assertThat(assertTargetRing)

// filtering a collection before asserting

.filteredOn(e -> e.getTitle().startsWith(“title”))

.extracting(AssertTarget::getNum)

.contains(16, 18);

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

学习分享,共勉

这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!

资料整理不易,读者朋友可以转发分享下!

Java核心知识体系笔记.pdf

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

中高级Java开发面试高频考点题笔记300道.pdf

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

架构进阶面试专题及架构学习笔记脑图

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

Java架构进阶学习视频分享
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
img-uD7IF4yb-1712858761485)]

[外链图片转存中…(img-QuLfHCI0-1712858761485)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

学习分享,共勉

这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!

资料整理不易,读者朋友可以转发分享下!

Java核心知识体系笔记.pdf

[外链图片转存中…(img-xNTtowQh-1712858761485)]

中高级Java开发面试高频考点题笔记300道.pdf

[外链图片转存中…(img-bo2D4pyB-1712858761486)]

架构进阶面试专题及架构学习笔记脑图

[外链图片转存中…(img-E0Ei8dA7-1712858761486)]

Java架构进阶学习视频分享
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值