单元测试系列 | 如何更好地测试依赖外部接口的方法

这篇具有很好参考价值的文章主要介绍了单元测试系列 | 如何更好地测试依赖外部接口的方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

在现在这个微服务时代,我们项目中经常都会遇到很多业务逻辑是依赖其他服务或者第三方接口。工作中各位同学对于这类型场景的测试方式也是五花八门,有些是直接构建一个外部mock服务,返回一些固定的response;有些是单元测试都不写,直接利用IDE工具,通过debug模式调用依赖服务接口,然后自己在程序运行时插入假的返回数据或者直接粗暴调用依赖服务接口去调试自己逻辑;有些是通过单元测试,使用mockito去屏蔽外部依赖等。

刚好最近有位精神小伙跟我反馈了一个问题,他改完代码就部署到SIT进行集成测试,结果服务运行时一调用接口就报错,因此被测试同学投诉没做好单元测试就部署,也被老大痛骂一顿。他觉得很委屈,觉得明明在做单元测试时已经针对同样的Mock数据测试过,结果在服务器上代码运行到restTemplate.exchange方法就报错,直接转换类型失败,他想知道为什么他的单元测试没覆盖到。

这里主要讲解下mockitoSpring Test两种常见单元测试场景。以下用例均为模拟场景和测试数据。

场景

以下是模拟该精神小伙的代码片段:

@Service
public class CustomerServiceImpl implements CustomerServiceI {

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public List<Customer> getCustomerList() {
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        HttpEntity<String> entity = new HttpEntity<String>(headers);
        ParameterizedTypeReference<List<Customer>> responseBodyType = new ParameterizedTypeReference<List<Customer>>(){};
        return restTemplate.exchange("http://localhost:8080/customers", HttpMethod.GET, null,responseBodyType).getBody();
    }

}

以下是针对该代码逻辑的单元测试:

@ExtendWith(MockitoExtension.class)
public class CustomerServiceImplTest {

    @Mock
    private RestTemplate restTemplate;

    @InjectMocks
    private CustomerServiceI customerServiceI = new CustomerServiceImpl();


    @Test
    void testGetCustomerList() {
        //given
        Customer customer1 = new Customer(1, "Evan");
        Customer customer2 = new Customer(2, null);
        List<Customer> customerList = new ArrayList<Customer>();
        customerList.add(customer1);
        customerList.add(customer2);
        ParameterizedTypeReference<List<Customer>> responseBodyType = new ParameterizedTypeReference<List<Customer>>(){};
        //When
        Mockito.when(restTemplate.exchange("http://localhost:8080/customers", HttpMethod.GET, null,responseBodyType)).thenReturn(new ResponseEntity(customerList, HttpStatus.OK));

        //expect
        List<Customer> customers = customerServiceI.getCustomerList();

        Assertions.assertEquals(customerList,customers);
    }

}

测试数据

[
  {
    "id": "1",
    "name": "Evan"
  },
  {
    "id": "2",
    "name": null
  }
]

测试能顺利通过
单元测试依赖,技术教程,单元测试,java,junit
大家会发现,他这里的单元测试,如果只针对GetCustomerList这个方法进行测试并没有多大问题,因为他把外表的依赖已经Mock掉了,该单元测试测试对象就是customerServiceI.getCustomerList()这个方法,很多同学平时也是这样子做。

问题

其实他的测试代码并没有太大问题,问题在于他想要的测试对象是谁。如果他的测试对象只是关注customerServiceI.getCustomerList()返回是不是正常,也就是这个方法业务逻辑是否正常,不需要关注restTemplate对接口调用过程,那么他这个单元测试没有问题。但是在这里,他有一个隐藏的测试用例,就是该方法从调用依赖服务接口到接收返回数据的整个方法生命周期是否正常,也就是需要关注restTemplate对接口调用过程。

听起来有点绕,那么这里的区别是什么?
这里的区别就是 是否需要关注接口调用这个过程

 Mockito.when(restTemplate.exchange("http://localhost:8080/customers", HttpMethod.GET, null,responseBodyType)).thenReturn(new ResponseEntity(customerList, HttpStatus.OK));

很多时候,我们单元测试可能不会关注接口调用,也会像上面例子那样子,直接把restTemplate操作mock掉,这时候我们只需要关注我们写的代码逻辑是否符合预期。但问题也是出在这里,因为我们把restTemplate.exchange整个过程以及返回值mock掉了,所以这里的单元测试并没有真正调用restTemplate.exchange方法,它只是按照我们写的data直接返回而已,这也就是为什么在单元测试的时候没有测试出来。

改进

在现有的基础上增加一个单元测试用例,主要覆盖该方法从调用依赖服务接口到接收返回数据的整个方法生命周期是否正常这场景。

以下是针对上面代码逻辑增加的一个单元测试用例:

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = SpringTestConfig.class)
public class CustomerServiceMockRestServiceServerUnitTest {

    @Autowired
    private CustomerServiceI customerServiceI;

    @Autowired
    private RestTemplate restTemplate;

    @Value("classpath:mockdata/response.json")
    Resource mockResponse;

    private MockRestServiceServer mockServer;
    private ObjectMapper mapper = new ObjectMapper();

    @BeforeEach
    public void init() {
        mockServer = MockRestServiceServer.createServer(restTemplate);
    }

    @Test
    void givenMockingIsDoneByMockCustomerRestServiceServer_whenGetIsCalled_thenReturnsMockedCustomerListObject() throws Exception {
        //given
        Customer customer1 = new Customer(1, "Evan");
        Customer customer2 = new Customer(2, null);
        List<Customer> customerList = new ArrayList<Customer>();
        customerList.add(customer1);
        customerList.add(customer2);
        //when
        mockServer.expect(ExpectedCount.once(),
                requestTo(new URI("http://localhost:8080/customers")))
                .andExpect(method(HttpMethod.GET))
                .andRespond(withStatus(HttpStatus.OK)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body( FileUtils.readFileToString(mockResponse.getFile(), Charset.forName("utf-8")))
                );

        List<Customer> customers = customerServiceI.getCustomerList();
        mockServer.verify();
        //expect
        Assertions.assertEquals(customerList, customers);
    }
}

从上面这个测试用例可以看到,这里并没有mockrestTemplateCustomerServiceI,只是使用了mock server把接口返回数据mock掉了,跟上面最大的区别是这里测试会启动spring容器,并且调用真实restTemplate实例进行调用,可以模拟真实的 API调用,此时restTemplate.exchange会被真实执行,相当于是调用了一个外部Mock API服务拿到一个预定义的返回数据。

这里使用上面同样的测试数据,通过注解注入外部Json文件:

@Value("classpath:mockdata/response.json")
    Resource mockResponse;

测试数据

[
  {
    "id": "1",
    "name": "Evan"
  },
  {
    "id": "2",
    "name": null
  }
]

测试结果
单元测试依赖,技术教程,单元测试,java,junit
这里你会发现,测试结果跟上面不一样,这里在单元测试restTemplate调用时就已经暴露问题了,因为Customer的属性都加了NonNull注解,因此在类型转换的时候,由于测试数据包含Null值,所以调用 restTemplate.exchange方法时尝试转换成Customer对象时失败,由于上面第一个单元测试它直接把restTemplate实例 mock掉了,因此单元测试可以直接通过,而第二个单元测试是会直接使用restTemplate进行接口调用,可以更真实模拟接口调用情况。

@Data
public class Customer {
    @NonNull
    private int id;
    @NonNull
    private String name;

    public Customer(){

    }

    public Customer(int id,String name){
            this.id=id;
            this.name =name;
    }

}

对于第二个单元测试,只需要使用的测试数据均为非Null值,就可以测试通过

[
  {
    "id": "1",
    "name": "Evan"
  },
  {
    "id": "2",
    "name": "Alin"
  }
]

测试代码也要改为非Null

    //given
        Customer customer1 = new Customer(1, "Evan");
        Customer customer2 = new Customer(2, "Alin");
        List<Customer> customerList = new ArrayList<Customer>();
        customerList.add(customer1);
        customerList.add(customer2);

测试结果
单元测试依赖,技术教程,单元测试,java,junit

结论

单元测试方法有很多,但针对外部接口依赖测试方法,以上两种比较常见。第一种单元测试更偏向于方法结果的测试,不关注接口调用过程,因为里面有第三方依赖,一般都会直接mock掉,只关注非外部依赖部分的代码逻辑。而第二种单元测试,会关注接口调用,覆盖了整个方法执行过程,所以一旦接口调用有问题更容易发现,但是有些同学也喜欢把这部分设计外部接口依赖的逻辑放在集成测试的时候再测试。大家这里就根据自己实际情况进行选择即可,一般以上两种单元测试用例结合使用覆盖更全。

注意:以上的测试数据一般是由接口提供者提供或者根据API接口文档定义生成,这样才能更好模拟真实API接口返回的数据。

代码

这里是本文的测试代码,供大家参考。文章来源地址https://www.toymoban.com/news/detail-765686.html

  • https://github.com/EvanLeung08/java-unit-test-samples/tree/main/spring-web/spring-resttemplate-unit-test

到了这里,关于单元测试系列 | 如何更好地测试依赖外部接口的方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 接口测试系列(二)Postman如何传递list、map对象等参数

    请求参数一般为简单文本text,包含基本数据类型,如数字和字符串,填写在Params页签下。发送请求时,将自动加在地址栏后方。 基本数据类型,只需要将相应的值填写在对应的value区域即可。内容类型需选择x-www-form-urlencoded。 内容类型需选择form-data,在key区域,需选择File类型

    2024年02月05日
    浏览(46)
  • 如何编写接口自动化框架系列之unittest测试框架的详解(二)

    在编写自动化框架过程中 ,我们首先想到的就是选择一个合适的测试框架 ,目前常用的测试框架有unittest和pytest , unittest比较简单,适合入门着学习 ;而pytest比较强大,适合后期进阶 。本文主要介绍的就是unittest框架 。接下来 ,我们从以下三个问题开始说明: unittest是什么

    2024年02月07日
    浏览(62)
  • 如何编写接口自动化框架系列通过yaml来管理测试用例(四)

    本文是接口自动化测试框架系列篇的第四篇 ,主要介绍yaml包的使用 。自动化测试的本质是将功能测试用例交给代码去 目录 1. yaml介绍? 2.python中的yaml包 3.项目中使用yaml包 4 项目总结 执行 ,测试人员往往是在自动化框架添加对应的测试用例即可(也叫测试脚本)。而维护测试

    2024年02月07日
    浏览(60)
  • 解决Spring Boot单元测试中@Autowired依赖注入失效的问题

    本文介绍了在Spring Boot单元测试中使用@Autowired注入的方法中,由于使用反射导致依赖注入失效的问题,以及如何使用AutowiredAnnotationBeanPostProcessor手动处理依赖注入来解决这个问题。 在Spring Boot的单元测试中,我们经常使用@Autowired注解来自动注入需要测试的对象或依赖。然而,

    2024年02月03日
    浏览(56)
  • 实战指南:使用 xUnit.DependencyInjection 在单元测试中实现依赖注入【完整教程】

    上一篇我们创建了一个 Sample.Api 项目和 Sample.Repository ,并且带大家熟悉了一下 Moq 的概念,这一章我们来实战一下在 xUnit 项目使用依赖注入。 Xunit.DependencyInjection 是一个用于 xUnit 测试框架的扩展库,它提供了依赖注入的功能,使得在编写单元测试时可以更方便地进行依赖注

    2024年04月15日
    浏览(45)
  • Java~在maven项目中添加junit依赖实现单元测试(@After

    在main目录下新建一个test目录, 并将它设置为Test Sources Root 实现 在pom.xml中导入依赖 下载单元测试的插件 3. 双击选择你要测试的类, 使用快捷键ctrl+shift+t 4. 点击create, 然后更改如下画面junit4, 选中你要测试的方法, 单元测试的生成类默认保存在test目录下 5. 进入生成的测试类就可

    2024年04月27日
    浏览(37)
  • 单元测试、模块测试、web接口测试

    然而在功能的实现代码中并没有“单元”,也没有“模块”;只有函数、类和方法。先来分别看看它们 的定义: 单元测试(Unit testing),是指对软件中的最小可测试单元进行检查和验证。通常该代码块单独和孤立 的,如果您的测试使用了一些外部资源,如网络或数据库,它

    2024年02月06日
    浏览(40)
  • Python中如何编写接口,以及如何请求外部接口

    Python是一种既简单又强大的编程语言,也是现代软件开发的重要工具之一。在开发过程中,我们通常需要编写接口,并且从外部接口获取数据。本文将介绍如何在Python中编写接口,以及如何请求外部接口。 在Python中,可以使用Flask和Django等框架来编写接口。本文以Flask为例,

    2024年02月03日
    浏览(32)
  • 7、单元测试--测试RestFul 接口

    – 测试用例类使用@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)修饰。 – 测试用例类会接收容器依赖注入TestRestTemplate这个实例变量。 – 测试方法可通过TestRestTemplate来调用RESTful接口的方法。 测试用例应该定义在和被测试类位于同一个包位置。 解释: 注意点1: 涉及注解:

    2024年02月14日
    浏览(36)
  • 吐血整理,接口自动化测试-接口依赖/上传接口处理(项目实例)

    常见的两种接口依赖处理方式 1、请求体的字段依赖 这种情况多数是在当前测试的接口,它的前置接口的请求体中的字段要拿来在当前的接口请求体中继续使用。 比如修改用户信息的接口,该接口会使用到用户名的字段,该字段是由创建用户时的请求体中传入的,创建用户的

    2024年02月12日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包