欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Springboot集成测试MockBean踩坑记录

程序员文章站 2024-02-10 08:44:58
...

前提:之前给自己的代码写单元测试习惯使用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^