Springboot单元测试:SpyBean vs MockBean

发表于:2020-10-19 09:50

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:chengco    来源:掘金

  问题是什么?
  如下是待测试类,期望测试TestService的test方法,但是由于某种原因(下例中的doSomething)无法简单的被执行。 所以希望test方法真实执行,而为doSomething方法打桩。
package com.example.demo.service;

import com.example.demo.repositroy.TestRepository;
import org.springframework.stereotype.Component;

@Component
public class TestService {
    private final TestRepository testRepository;

    public TestService(TestRepository testRepository) {
        this.testRepository = testRepository;
    }

    public String doSomething(){
        //假装有复杂的无法执行的业务逻辑
        testRepository.doSomething();
        throw new RuntimeException();
    }

    public String test() {
        doSomething();
        //其他逻辑
        return "id";
    }
}
  如何解决?
  使用MockBean
  如果仅使用@MockBean将修饰的对象mock掉,这样TestService的doSomething()方法就不再执行具体的细节,但是MockBean会将目标对象的所有方法全部mock,所以test不能真实地被执行,也就无法测试了。
  而when...thenCallRealMethod可达到部分mock的效果,仅test方法真实执行。
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestServiceTest {
    @MockBean
    TestService testService;

    @Test
    public void test(){
        when(testService.test()).thenCallRealMethod();
        assertThat(testService.test(), equalTo("id"));
    }
}
  小陷阱
  与使用@MockBean不同,上节中调用doReturn("").when(testService).doSomething() 时doSomething方法被打桩。而when(testService.doSomething()).thenReturn("")则达不到此效果。原因是:使用@SpyBean修饰的testService是一个真实对象,所以testService.doSomething()会被真实调用。
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestServiceTest {
    @SpyBean
    TestService testService;

    @Test
    public void test(){
        doReturn("").when(testService).doSomething();
        assertThat(testService.test(), equalTo("id"));
    }
}
  Mockito官方文档上这样说:
  Sometimes it's impossible or impractical to use when(Object) for stubbing spies. Therefore when using spies please consider doReturn|Answer|Throw() family of methods for stubbing. Example:
List list = new LinkedList();
  List spy = spy(list);
  //Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
  when(spy.get(0)).thenReturn("foo");
  //You have to use doReturn() for stubbing
  doReturn("foo").when(spy).get(0);
  Mockito *does not* delegate calls to the passed real instance, instead it actually creates a copy of it. So if you keep the real instance and interact with it, don't expect the spied to be aware of those interaction and their effect on real instance state. The corollary is that when an *unstubbed* method is called *on the spy* but *not on the real instance*, you won't see any effects on the real instance.
  Watch out for final methods. Mockito doesn't mock final methods so the bottom line is: when you spy on real objects + you try to stub a final method = trouble. Also you won't be able to verify those method as well.
  概括一下:
  当使用spy时,考虑使用`doReturn|Answer|Throw()`
  spy修饰的变量,Mockito会重新创建一个实例的copy,并不直接作用于真实实例
  spy对final方法无效
  SpyBean vs MockBean
  `SpyBean`和`MockBean`是`spring-boot-test`包所提供的两个注解,用于Spy或Mock Spring容器所管理的实例。而Spy与Mock的方式正好相反,spy默认所有方法均真实调用,Mock默认所有方法均调用mock的实现。
  使用场景上有什么区别呢?基于上例,虽然两者都能实现,但`SpyBean`更合适,因为上例在测试`TestService`,所以`testService`不应该是一个完全被mock的实例。而如果在`TestService`的测试用力中想要Mock `TestRepository`,使用`MockBean`就比较合适了。
  >为测试主体类部分打桩考虑使用`SpyBean`, 为外部依赖打桩,考虑使用`MockBean`。
  总结
  `SpyBean`和`MockBean`是`spring-boot-test`包所提供的两个注解,用于Spy或Mock Spring容器所管理的实例;
  使用`SpyBean`或者`Spy`时,当需要对某个方法进行打桩时,需要注意一些使用限制;
  为测试主体类部分打桩考虑使用`SpyBean`,为外部依赖打桩,考虑使用`MockBean`。

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号