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