热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

spring中使用Mockito解决Bean依赖树问题方法

在本篇文章里小编给各位整理了关于spring中使用Mockito解决Bean依赖树问题方法,有需要的朋友们可以参考下。

前提

本文不是针对Mockito的入门教学 ,主要叙述如何简单的使用Mockito解决Bean依赖树问题,对于Mockito的学习请找其他的文章或者查阅官方文档

基本概念 Junit初始化及存在的问题

spring应用在unit test时,test是独立运行的,所以需要自行 init ApplicationContext,启动 Ioc容器。

Junit要求:Test类中涉及的所有Spring bean 注入成功才能完成applicationContext初始化,并启动IOC容器,否则无法执行unit test。

ApplicationContext初始化的两种方式 手动注入(使用 @Bean或者 @Component 注入所需的类)编写@Configuration 类(使用@ComponentScan 指定扫描beans) 两种初始化方式存在的问题

方式一:

所需的beans中,一个bean少注入了就会导致无法初始化上下文需要注入的bean太多时,需要花费大量的时间和精力,排查缺漏难度大

方式二:

颗粒度难以把控,随着项目规模变大之后,可能导致bean导入过多,单元测试跑很久才能通过当项目规模大了之后,bean之间的依赖往往是复杂的,扫描bean的方式可能出现一些不属于自己模块的未知问题或者某些中间件在unitTest环境无法正常启动,导致无法初始化上下文 什么是依赖树?

在开发应用时,往往会出现如上图的 树型依赖 ,比如 serviceA 调用 serviceB,serviceB 又调用 serviceC 。

然而这只是一个简单的例子。真正的开发中,往往一个 service 会依赖多个 service ,以及多个 dao ,以此来实现业务逻辑。

而根据Junit要求,我们必须将树的路径经过的所有节点(bean)都注入才能完成spring上下文初始化。这时如果bean之间的依赖耦合过大时,就无法跳脱出两种初始化方式带来的问题。

什么是Mockito?

在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟对象(Mock 对象)来创建以便测试的测试方法。

Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。

简单来说:就是虚拟一个mock对象,这个对象在单元测试时会“狸猫换太子”,将原有bean进行替换,“骗过”spring初始化,成功启动ioc容器,以此规避常规初始化方式带来的种种问题。

开发场景

结合本人在工作中遇见的问题,当时我所写的模块进行unitTest时,就出现了依赖树过于庞大的问题。

首先,我采用了常规的手动注入(方式一),导致注入了很久都没注入完,无法执行测试。后来觉得这方法在这种情况不可行。然后,我采用了编写@Configuration 类(方式二),同样也存在一些问题。一些不属于我负责模块的bean也被注入,其中某些涉及TaskSchedule的bean无法被正确注入,导致无法执行测试。此时一个个bean探索,解决问题显然不现实。最后,我采用Junit+Mockito结合的方式进行单元测试。按照依赖树大小进行区分。 依赖树小的直接使用常规的手动注入(方式一),省事,同时保证大部分逻辑按照代码正常运行依赖树大的使用Mockito,避免前文提到的两种初始化方式导致的问题

使用 1 导入maven依赖

首先导入mockito maven依赖,版本请根据自己的spring版本选择,否则会出现不兼容的情况。


      org.springframework.boot
      spring-boot-starter-test
      test
      
        
          org.junit.vintage
          junit-vintage-engine
        
      
    
    
      junit
      junit
      4.12
      test
    

注意:

此处导入了spring-boot-starter-test是因为这个依赖已经包含了mockito相关的jar包

spring-boot-starter-test可以使用 @MockBean 注解(mockito-core、mockito-all貌似不能)

@Mock和@MockBean的区别:

@Mock @MockBean mock bean替换时机 spring上下文初始化 完成之后 spring上下文初始化 执行期间 能否“骗”过spring初始化否是 能否解决依赖树否是 在没注入所有所需的bean,无法完成spring上下文初始化时,@Mock无法正常工作 @MockBean在初始化时就进行替换,spring上下文初始化时检测的bean为替换后的mock bean,而mock bean本身是无依赖任何其他bean的,自然能够“骗”过spring上下文初始化阶段,成功启动IOC容器 2 分析bean之间的依赖

使用一个简单的Demo进行开发场景的模拟,采用Junit+Mockito结合的方式进行单元测试,根据依赖树大小区分出是否需要mock

如图,此处编写了一个ControllerA,ControllerA中依赖了2个bean:ServiceA,DaoA

分析过程: 关于 DaoA :由于Dao往往不会依赖其他的bean,所以此处可以使用常规的手动注入(方式一)即可。方便快捷关于 ServiceA :由于serviceA依赖了serviceB(->DaoB)、serviceC(->DaoC),像这样的嵌套依赖的bean就可以使用Mockito,来解决依赖树问题 3 编写Test类

daoA使用@Bean注解注入即可

@Bean
    public DaoA daoA(){
      return new DaoAImpl();
    }

1.serviceA首先使用@MockBean注解,将serviceA模拟为Mock Bean,它将在spring上下文初始化时就替换掉原有Bean

 @MockBean
  private ServiceA serviceA;

2.在test类执行前(@Before),使用Mockito API设置调用某个方法的返回值(你预期得到的返回结果),在Test类中调用这个方法时就会返回所指定的值

@Before
  public void init(){
    MockitoAnnotations.initMocks(this);//只使用 @MockBean 时可省略这句
    when(controllerA.serviceA_method()).thenReturn("666");
  }

3.使用 @InjectMocks 通知依赖了serviceA的controllerA,在spring启动时,对controllerA这个bean进行相应的后置处理

@Autowired
  @InjectMocks
  private ControllerA controller;

4.单元测试时,就不会使用原有Bean的方法,而是使用Mock Bean及其已经指定了返回值的方法

@Test
  public void testDeepMock() {
    String s = controllerA.serviceA_method();
    System.out.println(s);
  }

5.unitTest结果


以上就是本次介绍的全部相关知识点,感谢大家的学习和对的支持。


推荐阅读
  • ABP框架是ASP.NET Boilerplate的简称,它不仅是一个开源且文档丰富的应用程序框架,还提供了一套基于领域驱动设计(DDD)的最佳实践架构模型。本文将详细介绍ABP框架的特点、项目结构及其在Web API优先架构中的应用。 ... [详细]
  • 为何Compose与Swarm之后仍有Kubernetes的诞生?
    探讨在已有Compose和Swarm的情况下,Kubernetes是如何以其独特的设计理念和技术优势脱颖而出,成为容器编排领域的领航者。 ... [详细]
  • Docker安全策略与管理
    本文探讨了Docker的安全挑战、核心安全特性及其管理策略,旨在帮助读者深入理解Docker安全机制,并提供实用的安全管理建议。 ... [详细]
  • 本文详细介绍如何在华为鲲鹏平台上构建和使用适配ARM架构的Redis Docker镜像,解决常见错误并提供优化建议。 ... [详细]
  • 探讨密码安全的重要性
    近期,多家知名网站如CSDN、人人网、多玩、开心网等的数据库相继被泄露,其中大量用户的账户密码因明文存储而暴露无遗。本文将探讨黑客获取密码的常见手段,网站如何安全存储用户信息,以及用户应如何保护自己的密码。 ... [详细]
  • 本文将探讨如何在 Struts2 中使用 ActionContext 和 ServletActionContext 来获取请求参数和会话信息,同时解释它们的内部机制和最佳实践。 ... [详细]
  • RTThread线程间通信
    线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ... [详细]
  • 本文探讨了为何产品团队提出的某些需求在研发完成后未能获得用户的认可,并提供了改进方法。主要分析了功能不完整或存在bug以及用户体验不佳的原因。 ... [详细]
  • 说明Python教程正在编写中,欢迎大家加微信sinbam提供意见、建议、纠错、催更。drymail是一个邮件发送库,封装了Python的smtplib ... [详细]
  • 深入理解Java多线程与并发机制
    本文探讨了Java多线程和并发机制的核心概念,包括多线程类的分类、执行器框架、并发容器及控制工具。通过详细解析这些组件,帮助开发者更好地理解和应用多线程技术。 ... [详细]
  • Spring 中策略模式的应用:Resource 接口详解
    本文探讨了在 Spring 框架中如何利用 Resource 接口实现资源访问策略。Resource 接口作为资源访问策略的抽象,通过多种实现类支持不同类型的资源访问。 ... [详细]
  • 使用Tkinter构建51Ape无损音乐爬虫UI
    本文介绍了如何使用Python的内置模块Tkinter来构建一个简单的用户界面,用于爬取51Ape网站上的无损音乐百度云链接。虽然Tkinter入门相对简单,但在实际开发过程中由于文档不足可能会带来一些不便。 ... [详细]
  • 通过马老师的视频学习了Java中的容器相关内容,包括Collection、Set、List、Map及其常见实现类,并深入了解了这些容器的基本操作方法。 ... [详细]
  • Cookie学习小结
    Cookie学习小结 ... [详细]
  • 为什么多数程序员难以成为架构师?
    探讨80%的程序员为何难以晋升为架构师,涉及技术深度、经验积累和综合能力等方面。本文将详细解析Tomcat的配置和服务组件,帮助读者理解其内部机制。 ... [详细]
author-avatar
丶敷衍怎么演彡_175
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有