背景

为了确保代码的质量,对编写的代码进行单元测试是非常有必要的。

在JAVA项目中,一般的项目结构比较复杂、依赖众多。在微服务与spring boot大行其道的今天,单纯靠junit来进行单元测试一般很难完成对模块的单元测试。

为了让JAVA项目中的单元测试更加灵活便于编写,各种mock框架应运而生,其中最为常用和经典的mock框架非mockito与powermock莫属。

为了快速入门,本文将通过几个实例让大家快速了解mokito与powermock的使用方法。

同时将对mokito与powermock中几个常见的疑问点通过几个实例来解答疑惑。

示例场景

在一个庞大的项目中,有一个service和一个dao,现在想在对它们进行单元测试,仅验证一下代码中的业务逻辑代码是否正确。

public interface UserService {
    String getUsernameById(int userId);
}


public class UserServiceImpl implements UserService {
    @Resource
    private UserDao userDao;

    @Override
    public String getUsernameById(int userId) {
        return userDao.getUsernameById(userId);
    }
}

public interface UserDao {
    String getUsernameById(int userId);
}

由于这两个类一在spring容器中,通常由于网络或环境原因,使用@SpringBootTest注解来将数据库或整个项目全准备好一般也不太方便,这里可能就需要mock来解决了。

@mock注解

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class UserServiceTest {
    @Mock
    private UserService userService;

    @Before
    public void testInit() {
        MockitoAnnotations.initMocks(this);
        when(userService.getUsernameById(1)).thenReturn("1111111111");
    }

    @Test
    public void getUsernameByIdTest() {
        String username = userService.getUsernameById(1);
        System.out.println(username);
    }

}

输出:

1111111111

从上面的代码中可以看出,被@mock修饰后的变量userService则会产生一个userService的mock类,不用@mock注解用mock方法来产生一个userService是一样的效果

@InjectMocks注解

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.when;

public class UserServiceTest {
    @Mock
    private UserDao userDao;
    @InjectMocks
    private UserServiceImpl userService;

    @Before
    public void testInit() {
        MockitoAnnotations.initMocks(this);
        when(userDao.getUsernameById(2)).thenReturn("2222222222");
    }

    @Test
    public void getUsernameByIdTest() {
        String username = userService.getUsernameById(2);
        System.out.println(username);
    }

}

输出:

2222222222

@InjectMocks注解修饰的类不能修饰抽象或接口类,被它修饰后,其被mock后的成员变量会注入到@InjectMocks的类中

比如上面的userDao则会被注入到userService中,当调用userService.getUsernameById方法时,原代码里的userDao.getUsernameById方法则会执行mock方法

spy

    @Test
    public void spyTest() {
        List<String> spyList = Mockito.spy(new ArrayList<>());
        when(spyList.size()).thenReturn(1000);
        spyList.add("abc");
        spyList.add("def");

        System.out.println(spyList.get(0));
        System.out.println(spyList.size());
    }

输出:

abc
1000

被spy修饰的方法默认会调用其真实的方法,如果有被mock限定了的条件才会先执行mock方法。

比如上面的例子,spyList这个list是被spy方法mock出来的,由于在mock声明时只mock了它的size方法,所以调用它的get方法时还会执行原来的逻辑

静态方法mock

有时在单元测试中会遇到需要对静态方法也进行mock,比如想要对下面这个IpUtils工具类进行mock,那么mock注解就无能为力了。

import java.net.InetAddress;
import java.net.UnknownHostException;

public class IpUtils {
    
    public static String getIp() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return null;
    }
}

mock静态方法

一般对于需要将静态方法进行mock可以借助于powermock来实现。

示例代码如下:

import org.junit.Before;
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.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mockStatic;

@RunWith(PowerMockRunner.class)
@PrepareForTest(IpUtils.class)
public class UserServiceTest {

    @Before
    public void init() {
        mockStatic(IpUtils.class);
        when(IpUtils.getIp()).thenReturn("192.168.1.123");
    }

    @Test
    public void testIp() {
        System.out.println(IpUtils.getIp());
    }

}

输出:

192.168.1.123

使用时需要先用@PrepareForTest注解将需要mock的静态类定义,之后再调用mockStatic方法进行mock即可。

上面的代码测试时的依赖如下:

		<dependency>
			<groupId>org.powermock</groupId>
			<artifactId>powermock-module-junit4</artifactId>
			<version>2.0.2</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.powermock</groupId>
			<artifactId>powermock-api-mockito2</artifactId>
			<version>2.0.2</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-core</artifactId>
			<version>2.23.4</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.powermock</groupId>
			<artifactId>powermock-api-mockito</artifactId>
			<version>1.7.4</version>
			<scope>test</scope>
		</dependency>

对桩模块与驱动模块概念的理解

在有了上面的示例代码体验后,再来理解一下什么是桩模块,什么是驱动模块

先看下百度百科上对桩模块的解释:

桩模块(Stub)是指模拟被测试的模块所调用的模块,而不是软件产品的组成的部分。主模块作为驱动模块,与之直接相连的模块用桩模块代替。在集成测试前要为被测模块编制一些模拟其下级模块功能的“替身”模块,以代替被测模块的接口,接受或传递被测模块的数据,这些专供测试用的“假”模块称为被测模块的桩模块。

那么根据上面的定义,将桩模块和驱动模块与@Mock注解和@InjectMocks进行对应,那么被@Mock修饰的模块则是桩模块,被@InjectMocks修饰的模块则是驱动模块

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐