Mockito和Groovy单元测试说明

单元测试说明

目前单元测试使用spring-boot-starter-test自带的Junit+Mockito框架,使用PowerMock针对Mockito进行功能增强。

单元测试需要保证以下几点:

  1. 不依赖外部系统
  2. 单元测试足够“单元”,避免流程过长的测试逻辑
  3. 测试方法的数据操作不影响真实数据源的数据现状
  4. 每个测试方法都要有verify或者Assert的验证或断言的操作,否则都是无效的测试方式
  5. 一些依赖外部系统的调用,或不需要每次单元测试都执行的测试类或测试方法,及时使用@Ignore,避免mvn test时执行单元测试异常
Mockito的问题
  1. mock数据不友好,数据结果对比也不友好的问题。
  2. 不支持final/static的类或方法的mock操作(已使用PowerMock增强字节码方式解决)
    • 但PowerMockRunner会重新加载Spring Context,如果配合SpringRunner使用,在集成单元测试阶段,
      会有Context重复加载问题。例如xxljob的端口被占用,因为SpringRunner在集成测试中会使用context缓存,
      以减少每个测试类重复加载上下文的消耗。
  3. 后续会进行其他单元测试框架调研的工作,主要考虑mock数据的生成和结果对比的友好程度。
Mockito基本使用说明

Mockito主要针对mock实际对象的行为,进行bean之间的依赖解耦处理。主要通过以下几个注解和方法进行mock操作。

1. @InjectMocks

@InjectMocks标记一个对象需要进行mock对象的注入,mock对象指被 @Mock 或者 @Spy 注解修饰的bean(后面说明这两个注解)。

@InjectMocks 修饰的对象一定是非接口对象,否则无法确定bean在初始化时需要mock哪些属性或方法。

@InjectMocks 注入mock对象的方式有两种:

  1. 添加 MockitoAnnotations.initMocks(this); 表示改测试类需要初始化mock对象并注入到 @InjectMocks 对象中。
  2. 如果使用了PowerMockRunner,则PowerMock会自动进行mock对象的注入,无须额外添加注入。测试类继承 com.jdh.fuhsi.BasePowerMockRunner 即可。
    (实际就是给类添加 @RunWith(PowerMockRunner.class)

例子1:CiticbankPushCustomerServiceImplTest

@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest
@Transactional
@Rollback
public abstract class BaseSpringJUnitRunner {
}

public class CiticbankPushCustomerServiceImplTest extends BaseSpringJUnitRunner {
    @InjectMocks
    private CiticbankPushCustomerServiceImpl citicbankPushCustomerService;
    @Mock
    private CiticbankHttpServerTemplate citicbankHttpServerTemplate;
    @Spy
    private CiticbankRegistNotifyServiceImpl citicbankRegistNotifyService;
    @Spy
    private JdhHttpServer jdhHttpServer;
    @Before
    public void setUp() {
        // 注入@Mock,@Spy对象到@InjectMocks中
        MockitoAnnotations.initMocks(this);
        request = JSONObject.parseObject(REQ_MESSAGE, RegisterApplyRequest.class);
        // 模拟回调成功
        Resp resp = RespUtil.success("mockSuccess");
        Mockito.doReturn(JSON.toJSONString(resp)).when(jdhHttpServer).simpleSend(anyString(), anyString(), any());
    }

    @Test
    public void testSuccessRequest() {
        // 具体的测试方法
        ....
    }

例子2:ChannelRequestRetryJobHandlerTest

// 基类标记了PowerMockRunner,使用PowerMock框架
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*"})
public abstract class BasePowerMockRunner {

}
@PrepareForTest(CommonUtil.class)
public class ChannelRequestRetryJobHandlerTest extends BasePowerMockRunner {
    // PowerMock会自动将下面的mock对象注入到channelRequestRetryJobHandler中
    @InjectMocks
    @Spy
    private ChannelRequestRetryJobHandler channelRequestRetryJobHandler = new ChannelRequestRetryJobHandler();
    /**
     * 模拟server
     */
    @Mock
    private DefaultHttpServerTemplate defaultHttpServerTemplate;
    @Mock
    private ChannelRequestLogService channelRequestLogService;
    @Mock
    private FileInfoNoticeService fileInfoNoticeService;
    ...
}
2. @Mock

@Mock会将对象的所有方法进行mock处理,一律默认返回null。可以对mock对象进行Mockito.when(…)设置模拟方法执行操作,
或Mcokito.verify(…)对mock对象的行为进行验证。

3. @Spy

@Spy标记的对象也是一个mock对象,不同的是,默认所有方法都返回原始对象的方法,只有被Mockito.when(…)进行stub之后的方法才会走mock方法。
同样也可执行Mcokito.verify(…)对mock对象的行为进行验证。

搭配@Autowired使用,可以将SpringBean注入到@InjectMocks中,同时还能对该对象进行Mockito.verify操作。

4. Mockito.when(…)

上述的注解是针对mock对象初始化,mock对象行为主要通过Mockito.when(…)方式进行,可以模拟方法返回,模拟异常等。模拟的行为称为stub。
以下用一个模拟返回方式,针对@Mock和@Spy的两种mock对象差异说明使用方式。

以下都是针对一个httpServer模拟返回成功对象的操作。

@Spy:

@Spy
private HttpServer httpServer;
...
Mockito.doReturn(JSON.toJSONString(resp)).when(httpServer).simpleSend(anyString(), anyString(), any());

@Mock:

@Mock
private CiticbankHttpServerTemplate citicbankHttpServerTemplate;
...
Response successResp = CommonUtil.buildSuccessResponse("mockSuccess");
Mockito.when(citicbankHttpServerTemplate.packageRequest(any(), any(), any(), anyBoolean())).thenReturn(successResp);

doReturn和thenReturn效果一致,区别在于前者不会真的执行mock对象对应的方法,而后者会执行mock对象的方法。
由于@Mock方式创建的对象所有方法都返回null,因此不会执行到真实方法;而@Spy如果没有进行stub操作,会执行真实方法。

stub的匹配规则:例子中出现的anyXXX(),表示这个mock方法在被何种入参场景调用时才会触发。

** null参数只能用any()识别,不能用anyString()

5. Mockito.verify(…)

每个单元测试的最后,都要针对一些数据进行Assert断言操作,例如
Assert.assertEquals("expected result", mockDate.getResult()); 来断言模拟数据的某些数据状态是否符合预期。
针对mock对象的行为验证,比如mock方法执行次数,是否抛出异常,等等,则可通过Mockito.verify(…)进行验证。

例如,以下验证mockObject这个对象的retry方法,匹配参数为any(),即任意对象,至少在前面的测试方法里至少被调用过一次:

@Mock
private T mockObject;
...测试方法逻辑...
Mockito.verify(mockObject, Mockito.atLeastOnce()).retry(any());
static/final方法的Mock方式

Mockito本身不支持static/final方法的mock,目前使用PowerMock增强Mockito的处理。PowerMock的缺点前面提到,因此使用时不建议搭配SpringRunner使用,否则集成单元测试执行会失败。

static/final方法的mock例子:

// 测试基类,也可以直接在对应测试类上使用下面的注解,效果相同
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*"})
public abstract class BasePowerMockRunner {

}

// 说明要mock的static/final方法所属的类
@PrepareForTest(CommonUtil.class)
public class ChannelRequestRetryJobHandlerTest extends BasePowerMockRunner {
    /**
     * 模拟server
     */
    @Mock
    private DefaultHttpServerTemplate defaultHttpServerTemplate;

    @Before
    public void setUp() {
        // 模拟返回静态方法,注入对应的Bean
        PowerMockito.mockStatic(CommonUtil.class);
        MockitoAnnotations.initMocks(this);
        // 基础数据初始化
        PowerMockito.when(CommonUtil.getBean(anyString())).thenReturn(defaultHttpServerTemplate);
    }
}

Groovy使用demo

def spyHandler = Spy(AutoEffectiveFactorQuotaHandler)
直接使用mock的service 然后测试里面的方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值