Table of Contents
关于
配置环境
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <powermock.version>2.0.2</powermock.version> </properties> <dependencies> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
应用
- 模拟静态方法: 模拟静态方法返回指定的值
- 模拟构造函数: 模拟构造函数返回的结果
- 模拟私有以及final方法
mock
对象对于未指定处理规则的调用会按照方法返回值类型返回该类型的默认值(int,long返回0, boolean返回false,对象则返回null,void则什么都不做)spy
对象对于未指定处理规则的时候会调用真实方法- 初始化静态field,
SuppressStaticInitializationFor
- 个人DEMO例子参考:https://github.com/tracholar/ml-homework-cz/tree/master/testing
Mock 外部调用
- 待测试的类 ClassUnderTest
public class ClassUnderTest { public void methodToTest(){ final long id = IdGenerator.generateNewId(); System.out.println(id); } }
这个待测试的类有一个外部调用 IdGenerator.generateNewId()
,为了测试,我们必须把这个外部调用mock掉,返回我们想要的值才能测试。可以通过 mockStatic
实现静态方法的mock,通过when(IdGenerator.generateNewId()).thenReturn(2L)
将静态方法返回的值设为给定的值,代码如下
import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.verifyStatic; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest(IdGenerator.class) public class TestClassUnderTest { @Test public void demoStaticMethodMocking() throws Exception { mockStatic(IdGenerator.class); when(IdGenerator.generateNewId()).thenReturn(2L); new ClassUnderTest().methodToTest(); } }
Mock非静态方法和对象(例如文件IO)
- 假设要测试的方法需要创建一个文件,这是一个非静态方法
import java.io.File; public class DirectoryStructure { public boolean create(String directoryPath) { File directory = new File(directoryPath); if(directory.exists()){ throw new IllegalArgumentException(directoryPath + " already exists."); } return directory.mkdirs(); } }
- 可以利用API
whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock)
实现创建对象的mock
import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.io.File; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; 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(DirectoryStructure.class) public class TestDirectoryStructure { @Test public void createDirectoryWhenNotExists() throws Exception { final String directoryPath = "mocked path"; File directoryMock = mock(File.class); whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock); when(directoryMock.exists()).thenReturn(false); when(directoryMock.mkdirs()).thenReturn(true); assertTrue(new DirectoryStructure().create(directoryPath)); verifyNew(File.class).withArguments(directoryPath); } }
- 代码:
Mock 私有字段
- 下面的例子有一个私有字段引用了外部对象
public class LocalServiceImpl { private ServiceA remoteService; public Node getRemoteNode(int num) { return remoteService.getRemoteNode(num); } }
- 可以通过
Whitebox.setInternalState(obj, field, mockObj);
来实现设置内部私有字段
import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.powermock.reflect.Whitebox; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Created by zuoyuan on 2019/7/19. */ @RunWith(MockitoJUnitRunner.class) public class TestLocalServiceImplMock { private LocalServiceImpl localService; private ServiceA remoteService; @Before public void setUp(){ localService = new LocalServiceImpl(); remoteService = Mockito.mock(ServiceA.class); Whitebox.setInternalState(localService, "remoteService", remoteService); } @Test public void testMock(){ Node target = new Node(1, "target"); Mockito.when(remoteService.getRemoteNode(1)).thenReturn(target); Node result = localService.getRemoteNode(1); assertEquals(target, result); assertEquals(1, result.getNum()); assertEquals("target", result.getName()); Node result2 = localService.getRemoteNode(2); assertNull(result2); } }
Mock私有方法
- 下面的例子要测试
meaningfulPublicApi
,但希望doTheGamble
返回特定的值来测试,需要mockdoTheGamble
方法,而执行真正的meaningfulPublicApi
方法。可以通过spy
来实现。spy
只mock指定的方法和属性,而其他没有mock的就会执行真正的方法!
public class CodeWithPrivateMethod { public void meaningfulPublicApi() { if (doTheGamble("Whatever", 1 << 3)) { throw new RuntimeException("boom"); } } private boolean doTheGamble(String whatever, int binary) { Random random = new Random(System.nanoTime()); boolean gamble = random.nextBoolean(); return gamble; } }
- 测试类
@RunWith(PowerMockRunner.class) @PrepareForTest(CodeWithPrivateMethod.class) public class CodeWithPrivateMethodTest { @Test(expected = RuntimeException.class) public void when_gambling_is_true_then_always_explode() throws Exception { CodeWithPrivateMethod spy = PowerMockito.spy(new CodeWithPrivateMethod()); when(spy, method(CodeWithPrivateMethod.class, "doTheGamble", String.class, int.class)) .withArguments(anyString(), anyInt()) .thenReturn(true); spy.meaningfulPublicApi(); } }
Mock 静态field
- 下面的例子有一个静态的字段
logger
,一般可能是一个远程的logger需要mock,这里以log4j为例
package com.tracholar.testing; import java.util.logging.Logger; public class StaticFieldTest { private static Logger logger = Logger.getLogger(StaticFieldTest.class.getName()); public void doSomething(){ logger.info("Hello"); } }
- 可以通过
@SuppressStaticInitializationFor
和@PrepareForTest
api来mock类的静态字段的初始化
import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; import java.util.logging.Logger; @RunWith(PowerMockRunner.class) @SuppressStaticInitializationFor("com.tracholar.testing.StaticFieldTest") @PrepareForTest(StaticFieldTest.class) public class TestStaticFieldTest { @Test public void test1(){ Whitebox.setInternalState(StaticFieldTest.class, "logger", Mockito.mock(Logger.class)); StaticFieldTest test = new StaticFieldTest(); test.doSomething(); } }
作业
- 纸上得来终觉浅,绝知此事要躬行,做个作业行不行。启动代码已经帮你写好,放在getstart 目录中,请完成测试用例中标记了TODO字样的部分。