Springboot集成测试MockBean踩坑记录

  • Post author:
  • Post category:其他

前提:之前给自己的代码写单元测试习惯使用Mokito、MockMvc,这次是需要对自己写的grpc接口进行一个集成测试,由于刚接触grpc,所以我需要通过集成测试保证grpc的调用是顺利的。由于集成测试mock的资料还是比较少,这次卡了两天,所以把自己这个过程中遇到的问题和解决方法记录下来供大家参考,也欢迎交流和指教~


在真实方法中,我需要使用RestTemplete 和 MongoTemplete等第三方依赖,对于MongoDB的存取需要进行mock,否则每一次测试都会操作Mongo,这是我不愿意看到的。

测试的调用关系大致是这样的:

grpcClient – blockingStub – xxxGrpc – xxxGrpcImp – service – serviceImp中的真实方法 – 调用mongoTemplete的方法。

1、首先我尝试了@Mock注解但测试时并没有进入到mock的方法中,依然向mongo中存入了数据。伪代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class Test {
    @Mock
    MongoTemplate mongoTemplate

    @Test
    private void test1(){
        Mockito.when(mongoTeplate.save()).thenReturn("mocksave");
        grpcClient.blockingStub.saveFile();
    }
}

2、接下来我尝试了使用Mockito,依然没有进入到mock的方法中。伪代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class Test {
    MongoTemplate mongoTemplate;
    
    @Before
    private void init(){
        mongoTemplate = mock(MongoTemplate.class);
    }

    @Test
    private void test1(){
        Mockito.when(mongoTeplete.save()).thenReturn("mocksave");
        grpcClient.blockingStub.saveFile();
    }
}

 在init()中加入了 MockitoAnnotations.initMocks(this); 依然无效。

看起来似乎是因为mock的bean没有注入到上下文中,没有装配进来,因此测试时依然走了真实的方法。

3.后来搜索时看到了MockBean的介绍——

在做单元测试时,如果想要 mock UserRepository 的逻辑,只需要声明一个变量并在上面加上 @MockBean 的注释即可,
之后使用 when().thenReturn() 来设定 mock UserRepository 的行为。
在运行时 SpringBoot 会扫描到你注释的 mock ,并自动装配到被测试的 controller 里面。
这也是和 @Mock 注释不同的地方,后者只能生成一个 Mock 类,但是并不能自动装配到其它类里面。

果然,使用@MockBean来mock mongoTemplete,测试就会走到我的真实方法中时注入mock 的mongoTemplete,从而避免对真实mongodb的数据存取。但是在mock MongoTemplete时会报错找不到GridFsTemplete,因此也mock了一下GridFsTemplete解决了这个问题。伪代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class Test {
    @MockBean
    GridFsTemplate gridFsTemplate;
    @MockBean
    MongoTemplate mongoTemplate;
    
    @Test
    private void test1(){
        Mockito.when(mongoTeplate.save()).thenReturn("mocksave");
        grpcClient.blockingStub.saveFile();
    }
}

由此看来,@Mock和Mockito更适合单元测试,因为他们只会生成一个mock类,但并不会自动装配,如果不直接调用mock类的方法,是无法进入mock的;而MockBean更适合集成测试,当调用走到mock对象时就会自动装配。

但是,当有多个测试类时,@MockBean注解会因不同的上下文从而导致springboot多次启动,参考文献——@MockBean的危害,文中提到

@MockBean的使用会导致每个application context中contextCustomizer的不同,
从而导致存储在context cache中的application context的uniquely key不同,
最终导致application context在测试类之间不能共享。

但我尝试文中的解决方法并没有奏效,Autowired时找不到Qualifier的那个Bean,不知道为什么……由于我是grpc测试,在第二个测试类启动时就会报错端口号已被占用,经过多次尝试后发现,@MockBean一样时上下文是一样的。因此我想出了一个不够优雅的方法——写一个基础类,将所有的MockBean放在里面,再让所有的测试类继承它。伪代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class BaseTest {
    @MockBean
    GridFsTemplate gridFsTemplate;
    @MockBean
    MongoTemplate mongoTemplete;
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class Test extends BaseTest{
    @Test
    private void test1(){
        Mockito.when(mongoTeplete.save()).thenReturn("mocksave");
        grpcClient.blockingStub.saveFile();
    }
}

这样一来,所有的测试类有相同的@MockBean,上下文一致就会只启动一次springboot,暂时用了这样一个不够优雅的方法解决了这个问题。

期待有更好的解决方法,如果文章有什么纰漏也欢迎大家指出。^w^


版权声明:本文为zlx312原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。