Mockito 实战总结笔记

其他 mock 框架:powermock、easymock、jmock

mockito 官网:https://site.mockito.org

mockito 是一个单元测试框架。

Mockito 快速入门

下面来演示 Mockito 的入门案例。

包结构如下:
在这里插入图片描述

代码如下:

实体类 Account.java:

public class Account {
}

dao 层 AccountDao.java,该类中有一个接口,并且模拟数据库宕机的情况:

public class AccountDao {

    public Account findAccount(String username, String password) {
        throw new UnsupportedOperationException(); // 模拟数据库宕机情况
    }

}

controller 层 AccountLoginController.java,该类中有一个接口:

public class AccountLoginController {

    private final AccountDao accountDao;

    public AccountLoginController(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public String login(HttpServletRequest request) {
        final String userName = request.getParameter("username");
        final String password = request.getParameter("password");
        try {
            Account account = accountDao.findAccount(userName, password);
            if (account == null) {
                return "/login"; // 模拟返回登录页面
            } else {
                return "/index"; // 模拟登录成功
            }
        } catch (Exception e) {
            return "/505"; // 模拟出错情况
        }
    }

}

此时 controller 层的接口 login() 已经定义好了,现在要对其进行单元测试,但没有此时这个程序的环境中没有 servlet 容器,也没有数据库,如何进行测试?用 mockito !

AccountLoginControllerTest.java:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

import javax.servlet.http.HttpServletRequest;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;


@RunWith(MockitoJUnitRunner.class) // 使用mockito进行单元测试,记得加上这个注解
public class AccountLoginControllerTest {

    private AccountDao accountDao;
    private HttpServletRequest request;
    private AccountLoginController accountLoginController;

	// 每次测试开始前都会调用该方法,初始化上面定义的变量
    @Before
    public void setUp() {
        this.accountDao = Mockito.mock(AccountDao.class); // 创建一个AccountDao的“替身”
        this.request = Mockito.mock(HttpServletRequest.class); // 创建一个HttpServletRequest的“替身”
        this.accountLoginController = new AccountLoginController(accountDao);
    }

	// 测试返回"/index"的情况
    @Test
    public void testLoginSuccess() {
        Account account = new Account();
        // 意思是:当调用request.getParameter("username")时,让request.getParameter("username")返回"john"
        when(request.getParameter("username")).thenReturn("john"); 
        when(request.getParameter("password")).thenReturn("123456");
        // anyString():任意字符串
        when(accountDao.findAccount(anyString(), anyString())).thenReturn(account);
		
		// 通过断言来验证结果
        assertThat(accountLoginController.login(request), equalTo("/index"));
    }

	// 测试返回"/login"的情况
    @Test
    public void testLoginFailure() {
        when(request.getParameter("username")).thenReturn("john");
        when(request.getParameter("password")).thenReturn("123456");
        when(accountDao.findAccount(anyString(), anyString())).thenReturn(null);

        assertThat(accountLoginController.login(request), equalTo("/login"));
    }

	// 测试返回"/505"的情况
    @Test
    public void testLogin505() {
        when(request.getParameter("username")).thenReturn("john");
        when(request.getParameter("password")).thenReturn("123456");
        when(accountDao.findAccount(anyString(), anyString())).thenThrow(UnsupportedOperationException.class); // 注意这调的是thenThrow()

        assertThat(accountLoginController.login(request), equalTo("/505"));
    }

}

三种不同的 mock 方式

方式一:使用 @RunWith 注解
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;

import static org.mockito.Mockito.mock;

@RunWith(MockitoJUnitRunner.class)
public class MockByRunnerTest {

    @Test
    public void testMock() {
        AccountDao accountDao = mock(AccountDao.class);
        Account account = accountDao.findAccount("x", "x");
        System.out.println(account);
    }

}

打印结果:

null

如果不希望 account 为 null,可以将代码修改如下:

@Test
public void testMock() {
    AccountDao accountDao = mock(AccountDao.class, Mockito.RETURNS_SMART_NULLS);
    Account account = accountDao.findAccount("x", "x");
    System.out.println(account);
}

此时打印结果:

SmartNull returned by this unstubbed method call on a mock:
accountDao.findAccount("x", "x");
方式二:@Mock 注解
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class MockByAnnotationTest {

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Mock
    private AccountDao accountDao;

    @Test
    public void testMock() {
        Account account = accountDao.findAccount("x", "x");
        System.out.println(account);
    }

}

打印结果:

null

同样,若不想 account 为 null,可按如下编码:

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

public class MockByAnnotationTest {

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }
    
    @Mock(answer = Answers.RETURNS_SMART_NULLS)
    private AccountDao accountDao;

    @Test
    public void testMock() {
        Account account = accountDao.findAccount("x", "x");
        System.out.println(account);
    }

}

打印结果:

SmartNull returned by this unstubbed method call on a mock:
accountDao.findAccount("x", "x");
方式三:@Rule 注解
import org.junit.Rule;
import org.junit.Test;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import static org.mockito.Mockito.mock;

public class MockByRuleTest {

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Test
    public void testMock() {
        AccountDao accountDao = mock(AccountDao.class);
        Account account = accountDao.findAccount("x", "x");
        System.out.println(account);
    }

}

打印结果:

null

深度 Mock(Deep Mock)

下面的例子中使用 @Mock 注解来进行单元测试:

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

import static org.mockito.Mockito.when;

public class DeepMockTest {

    @Mock
    private PersonService personService;

    @Mock
    private Person person;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testDeepMock() {
        Person person = personService.get();
        person.foo(); // 此时会报空指针异常
    }

}

此时 testDeepMock() 测试无法通过,会报空指针异常,因为 person 为 null。

为了改进这个问题,将单元测试定义如下:

@Test
public void testDeepMock2() {
    when(personService.get()).thenReturn(person);
    Person person = personService.get();
    person.foo();
}

此时可以通过测试,完整代码如下:

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

import static org.mockito.Mockito.when;

public class DeepMockTest {

    @Mock
    private PersonService personService;

    @Mock
    private Person person;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testDeepMock() {
        Person person = personService.get();
        person.foo(); // 此时会报空指针异常
    }

	@Test
	public void testDeepMock2() {
	    when(personService.get()).thenReturn(person);
	    Person person = personService.get();
	    person.foo();
	}

}

对于上面代码,可以通过 Deep Mock 方式进行代码简化。简化后的代码如下:

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

public class DeepMockTest {

    @Mock(answer = Answers.RETURNS_DEEP_STUBS) // 指定为Deep Mock
    private PersonService personService;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testDeepMock() {
        personService.get().foo();
    }

}

Mockito Stubbing 语法详解

#未完待续

参考教程:汪文君Mockito实战视频

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值