java构造方法怎么mock,使用 JMockit 来 mock 构造函数

Java 测试的 Mock 框架以前是用 JMockit, 最近用了一段时间的 Mockito, 除了它流畅的书写方式,经常这也 Mock 不了,那也 Mock 不了,需要迁就于测试来调整实现代码,使得实现极不优雅。比如 Mockito 在 私有方法,final 方法,静态方法,final 类,构造方法面前统统的缴械了。powermock 虽然可作 Mockito 的伴侣来突破 Mockito 本身的一些局限,但是我一用它来 Mock 一个构造方法就出错

Caused by: java.lang.ClassNotFoundException: org.mockito.exceptions.Reporter

不得已再祭出 JMockit 这号称(也确实是)一无所不能的大杀器,在此见识一下它怎么 Mock 构造函数的

本篇实例所使用的 JMockit 版本是 1.30, 当前最新版 1.31, 由于尚未被 Maven 中央仓库收录,所以暂用 1.30。在 pom.xml 中如下方式引入

<dependency>

<groupId>org.jmockit</groupId>

<artifactId>jmockit</artifactId>

<version>1.30</version>

<scope>test</scope>

</dependency>

1

2

3

4

5

6

<dependency>

<groupId>org.jmockit</groupId>

<artifactId>jmockit</artifactId>

<version>1.30</version>

<scope>test</scope>

</dependency>

待测试的代码是类 Example, 代码如下

package cc.unmi;

public class Example {

public String findOneUser(String category) {

if("general".equals(category) || category.equals("admin")) {

return new UserService(new UserDao(), category).findById(123);

}

throw new RuntimeException("Invalid category");

}

}

1

2

3

4

5

6

7

8

9

10

11

packagecc.unmi;

publicclassExample{

publicStringfindOneUser(Stringcategory){

if("general".equals(category)||category.equals("admin")){

returnnewUserService(newUserDao(),category).findById(123);

}

thrownewRuntimeException("Invalid category");

}

}

它的 findOneUser(category) 方法中需要根据条件来创建一个 UserService 实例,所以未把 UserService 实例声明为 Example 的属性,通过 Example 的构造函数来传入,若如此就很容易用 Mockito 的 Mock 这个 UserService 实例了。

private UserService userService;

public Exmple(UserService userService) {

this.userService = userServie;

}

上面的实现是 Mockito 最喜爱的口味了。但由于 userService 并不跟随 Example 创建,所以 Mockito 去 Mock findOneUser(category) 里的 new UserService(userDao,  "admin") 就显示得捉襟见肘了。

在使用 JMockit mock UserService 构造函数之前,贴出一下 UserService 的演示实现

package cc.unmi;

public class UserService {

private UserDao userDao;

private final String category;

public UserService(UserDao userDao, String category) {

this.userDao = userDao;

this.category = category;

}

public String findById(int id) {

return userDao.findById(id);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

packagecc.unmi;

publicclassUserService{

privateUserDaouserDao;

privatefinalStringcategory;

publicUserService(UserDaouserDao,Stringcategory){

this.userDao=userDao;

this.category=category;

}

publicStringfindById(intid){

returnuserDao.findById(id);

}

}

我们要测试的目标方法是 Example.findOneUser(category), 其中一个测试是 userService 实例的 findById(id) 方法获得什么它也返回什么,所以单元测试中的 service.findById(id) 方法不应该调用实际的 userDao 的相应方法,也就是我们要 Mock 的目的所在。所以本例中的 UserDao 的方法并未实现,如下

package cc.unmi;

public class UserDao {

public String findById(int id) {

throw new RuntimeException("not implemented");

}

}

1

2

3

4

5

6

7

packagecc.unmi;

publicclassUserDao{

publicStringfindById(intid){

thrownewRuntimeException("not implemented");

}

}

那么来看测试代码 ExampleTest

package cc.unmi;

import mockit.Expectations;

import mockit.Mocked;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class ExampleTest {

@Mocked

private UserService userService;

@Test

public void testFindOneUser() {

new Expectations() {{

new UserService(withInstanceOf(UserDao.class), "admin");

result = userService;

userService.findById(123);

result = "Hello Yanbin's blog";

}};

Example example = new Example();

String user = example.findOneUser("admin");

assertEquals("Hello Yanbin's blog", user);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

packagecc.unmi;

importmockit.Expectations;

importmockit.Mocked;

importorg.junit.Test;

importstaticorg.junit.Assert.assertEquals;

publicclassExampleTest{

@Mocked

privateUserServiceuserService;

@Test

publicvoidtestFindOneUser(){

newExpectations(){{

newUserService(withInstanceOf(UserDao.class),"admin");

result=userService;

userService.findById(123);

result="Hello Yanbin's blog";

}};

Exampleexample=newExample();

Stringuser=example.findOneUser("admin");

assertEquals("Hello Yanbin's blog",user);

}

上面的测试 testFindOneUser() 顺利通过,这里的精髓就在

new UserService(withInstance(UserDao.class), "admin");

result = userService;

JMockit 只是把构造函数当成一个普通的有返回值的方法而已。

我们也可以换一种方式来 Mock 构造函数,用  new MockUp 的方式,用以植入 Mock 的内部变量值,下面的例子不直接 Mock UserService 实例,而是通 $init 函数来改变生成的 userService 实例的内部状态,以便对它内部的操作作进一步精细的控制。$init 在 JVM 中就就构造函数的表示法。

下面的例子,Mockito 和 JMockit 双管齐下,结合 JMockit 的强悍功能,以及 Mockito 的 BBD 风格,你也可以只使用 JMockit 的 Expectations API 来 mock 对 mockedUserDao 的操作。

package cc.unmi;

import mockit.Deencapsulation;

import mockit.Invocation;

import mockit.MockUp;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.mockito.runners.MockitoJUnitRunner;

import static org.junit.Assert.assertEquals;

import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)

public class ExampleTest {

@org.mockito.Mock

private UserDao mockedUserDao;

@Test

public void testFindOneUser()  {

new MockUp<UserService>() {

@mockit.Mock

public void $init(Invocation invocation, UserDao userDao, String category) {

UserService userService = invocation.getInvokedInstance();

Deencapsulation.setField(userService, mockedUserDao);

}

};

when(mockedUserDao.findById(123)).thenReturn("Hello Again");

Example example = new Example();

String user = example.findOneUser("admin");

assertEquals("Hello Again", user);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

packagecc.unmi;

importmockit.Deencapsulation;

importmockit.Invocation;

importmockit.MockUp;

importorg.junit.Test;

importorg.junit.runner.RunWith;

importorg.mockito.runners.MockitoJUnitRunner;

importstaticorg.junit.Assert.assertEquals;

importstaticorg.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)

publicclassExampleTest{

@org.mockito.Mock

privateUserDaomockedUserDao;

@Test

publicvoidtestFindOneUser() {

newMockUp<UserService>(){

@mockit.Mock

publicvoid$init(Invocationinvocation,UserDaouserDao,Stringcategory){

UserServiceuserService=invocation.getInvokedInstance();

Deencapsulation.setField(userService,mockedUserDao);

}

};

when(mockedUserDao.findById(123)).thenReturn("Hello Again");

Exampleexample=newExample();

Stringuser=example.findOneUser("admin");

assertEquals("Hello Again",user);

}

}

MockUp 和 Expectations API 其实也是 JMockit 的两种书写方式,前者可用于 mock 私有方法,后者常用,但功能稍弱一些。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值