c2c网站建设策划书,火车头 wordpress 4.9,长沙优化科技有限公司,研发项目管理系统spring-bean版本大约一年前#xff0c;我写了一篇博客文章如何模拟Spring Bean 。 所描述的模式对生产代码几乎没有侵入性。 正如读者Colin在评论中正确指出的那样#xff0c;基于Profile注释的间谍/模拟Spring bean是更好的选择。 这篇博客文章将描述这种技术。 我在工作中以… spring-bean版本 大约一年前我写了一篇博客文章如何模拟Spring Bean 。 所描述的模式对生产代码几乎没有侵入性。 正如读者Colin在评论中正确指出的那样基于Profile注释的间谍/模拟Spring bean是更好的选择。 这篇博客文章将描述这种技术。 我在工作中以及副项目中都成功使用了这种方法。 请注意在您的应用程序中普遍出现的嘲笑通常被视为设计气味。 介绍生产代码 首先我们需要测试代码以演示模拟。 我们将使用以下简单的类 Repository
public class AddressDao {public String readAddress(String userName) {return 3 Dark Corner;}
}Service
public class AddressService {private AddressDao addressDao;Autowiredpublic AddressService(AddressDao addressDao) {this.addressDao addressDao;}public String getAddressForUser(String userName){return addressDao.readAddress(userName);}
}Service
public class UserService {private AddressService addressService;Autowiredpublic UserService(AddressService addressService) {this.addressService addressService;}public String getUserDetails(String userName){String address addressService.getAddressForUser(userName);return String.format(User %s, %s, userName, address);}
} 当然这段代码没有多大意义但对于演示如何模拟Spring bean来说将是一个很好的选择。 AddressDao只是返回字符串因此模拟了从某些数据源的读取。 它自动连接到AddressService 。 该bean被自动连接到UserService 后者用于构造具有用户名和地址的字符串。 请注意我们将构造函数注入用作字段注入被认为是不好的做法。 如果要为应用程序强制执行构造函数注入Oliver GierkeSpring生态系统开发人员和Spring Data负责人最近创建了一个非常不错的项目Ninjector 。 扫描所有这些bean的配置是相当标准的Spring Boot主类 SpringBootApplication
public class SimpleApplication {public static void main(String[] args) {SpringApplication.run(SimpleApplication.class, args);}
}模拟Spring Bean无AOP 让我们在模拟AddressDao地方测试AddressService类。 我们可以通过Spring的Profiles和Primary批注创建这种模拟 Profile(AddressService-test)
Configuration
public class AddressDaoTestConfiguration {BeanPrimarypublic AddressDao addressDao() {return Mockito.mock(AddressDao.class);}
} 仅当Spring概要文件AddressService-test处于活动状态时才会应用此测试配置。 应用时它将注册AddressDao类型的bean该类型是Mockito创建的模拟实例。 Primary注释告诉Spring在有人自动装配AddressDao bean时使用此实例而不是实际实例。 测试类使用的是JUnit框架 ActiveProfiles(AddressService-test)
RunWith(SpringJUnit4ClassRunner.class)
SpringApplicationConfiguration(SimpleApplication.class)
public class AddressServiceITest {Autowired private AddressService addressService;Autowiredprivate AddressDao addressDao;Testpublic void testGetAddressForUser() {// GIVENMockito.when(addressDao.readAddress(john)).thenReturn(5 Bright Corner);// WHEN String actualAddress addressService.getAddressForUser(john);// THEN Assert.assertEquals(5 Bright Corner, actualAddress);}
} 我们激活配置文件AddressService-test以启用AddressDao AddressService-test 。 Spring集成测试需要使用RunWith注释而SpringApplicationConfiguration定义将使用哪个Spring配置来构建测试环境。 在测试之前我们会自动连接被测试的AddressService实例和AddressDao模拟。 如果您使用的是Mockito则随后的测试方法应明确。 在GIVEN阶段我们将所需的行为记录到模拟实例中。 在WHEN阶段我们执行测试代码在THEN阶段我们验证测试代码是否返回了我们期望的值。 监视Spring Bean无AOP 对于间谍示例将在AddressService实例上进行间谍 Profile(UserService-test)
Configuration
public class AddressServiceTestConfiguration {BeanPrimarypublic AddressService addressServiceSpy(AddressService addressService) {return Mockito.spy(addressService);}
} 仅当配置文件UserService-test处于活动状态时才会对此组件配置进行Spring扫描。 它定义了AddressService类型的主bean。 Primary告诉Spring使用这个实例以防在Spring上下文中存在两个这种类型的bean。 在构造此bean的过程中我们从Spring上下文自动装配了AddressService现有实例并使用Mockito的间谍功能。 我们正在注册的bean有效地将所有调用委派给了原始实例但是Mockito间谍程序使我们能够验证所监视实例上的交互。 我们将以这种方式测试UserService行为 ActiveProfiles(UserService-test)
RunWith(SpringJUnit4ClassRunner.class)
SpringApplicationConfiguration(SimpleApplication.class)
public class UserServiceITest {Autowiredprivate UserService userService;Autowiredprivate AddressService addressService;Testpublic void testGetUserDetails() {// GIVEN - Spring scanned by SimpleApplication class// WHENString actualUserDetails userService.getUserDetails(john);// THENAssert.assertEquals(User john, 3 Dark Corner, actualUserDetails);Mockito.verify(addressService).getAddressForUser(john);}
} 为了进行测试我们激活了UserService-test配置文件因此将应用我们的间谍配置。 我们自动装配UserService这是在测试和AddressService 目前正在通过窥探的Mockito。 我们不需要为在GIVEN阶段进行测试准备任何行为。 W HEN相被测明显执行代码。 在THEN阶段我们验证测试代码是否返回了期望的值以及是否使用正确的参数执行了addressService调用。 Mockito和Spring AOP的问题 假设现在我们要使用Spring AOP模块来处理一些跨领域的问题。 例如以这种方式记录对Spring Bean的调用 package net.lkrnac.blog.testing.mockbeanv2.aoptesting;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;import lombok.extern.slf4j.Slf4j;Aspect
Component
Slf4j
Profile(aop) //only for example purposes
public class AddressLogger {Before(execution(* net.lkrnac.blog.testing.mockbeanv2.beans.*.*(..)))public void logAddressCall(JoinPoint jp){log.info(Executing method {}, jp.getSignature());}
} 在从包net.lkrnac.blog.testing.mockbeanv2调用Spring bean之前将应用此AOP方面。 它使用Lombok的注释Slf4j记录调用方法的签名。 注意仅当定义了aop概要文件时才创建此bean。 我们正在使用此配置文件将AOP和非AOP测试示例分开。 在实际的应用程序中您不想使用此类配置文件。 我们还需要为应用程序启用AspectJ因此以下所有示例都将使用此Spring Boot主类 SpringBootApplication
EnableAspectJAutoProxy
public class AopApplication {public static void main(String[] args) {SpringApplication.run(AopApplication.class, args);}
} AOP构造由EnableAspectJAutoProxy启用。 但是如果我们将Mockito与Spring AOP结合进行模拟则此类AOP构造可能会出现问题。 这是因为两者都使用CGLIB代理真实实例并且当Mockito代理包装到Spring代理中时我们会遇到类型不匹配的问题。 这些可以通过使用ScopedProxyMode.TARGET_CLASS配置bean的作用域来ScopedProxyMode.TARGET_CLASS 但是Mockito的verify ()调用仍然会因NotAMockException失败。 如果我们为UserServiceITest启用aop配置文件则可以看到此类问题。 由Spring AOP代理的模拟Spring Bean 为了克服这些问题我们将模拟包装到这个Spring bean中 package net.lkrnac.blog.testing.mockbeanv2.aoptesting;import org.mockito.Mockito;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Repository;import lombok.Getter;
import net.lkrnac.blog.testing.mockbeanv2.beans.AddressDao;Primary
Repository
Profile(AddressService-aop-mock-test)
public class AddressDaoMock extends AddressDao{Getterprivate AddressDao mockDelegate Mockito.mock(AddressDao.class);public String readAddress(String userName) {return mockDelegate.readAddress(userName);}
} Primary注释可确保在注入过程中此bean优先于实际的AddressDao bean。 为了确保仅将其应用于特定测试我们为此bean定义了配置文件AddressService-aop-mock-test 。 它继承了AddressDao类因此可以完全替代该类型。 为了伪造行为我们定义了AddressDao类型的模拟实例该实例通过Lombok的Getter注释定义的getter Getter 。 我们还实现了readAddress()方法该方法有望在测试期间被调用。 此方法只是将调用委派给模拟实例。 使用该模拟程序的测试如下所示 ActiveProfiles({AddressService-aop-mock-test, aop})
RunWith(SpringJUnit4ClassRunner.class)
SpringApplicationConfiguration(AopApplication.class)
public class AddressServiceAopMockITest {Autowiredprivate AddressService addressService; Autowiredprivate AddressDao addressDao;Testpublic void testGetAddressForUser() {// GIVENAddressDaoMock addressDaoMock (AddressDaoMock) addressDao;Mockito.when(addressDaoMock.getMockDelegate().readAddress(john)).thenReturn(5 Bright Corner);// WHEN String actualAddress addressService.getAddressForUser(john);// THEN Assert.assertEquals(5 Bright Corner, actualAddress);}
} 在测试中我们定义AddressService-aop-mock-test配置文件以激活AddressDaoMock并定义aop配置文件以激活AddressLogger AOP方面。 为了进行测试我们自动测试bean的addressService及其伪造的依赖项addressDao 。 我们知道 addressDao将是AddressDaoMock类型的因为此bean被标记为Primary 。 因此我们可以将其mockDelegate转换mockDelegate行为记录到mockDelegate 。 当我们调用测试方法时应使用记录的行为因为我们希望测试方法使用AddressDao依赖项。 监视Spring AOP代理的Spring bean 类似的模式可用于监视实际实现。 这就是我们的间谍的样子 package net.lkrnac.blog.testing.mockbeanv2.aoptesting;import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;import lombok.Getter;
import net.lkrnac.blog.testing.mockbeanv2.beans.AddressDao;
import net.lkrnac.blog.testing.mockbeanv2.beans.AddressService;Primary
Service
Profile(UserService-aop-test)
public class AddressServiceSpy extends AddressService{Getterprivate AddressService spyDelegate;Autowiredpublic AddressServiceSpy(AddressDao addressDao) {super(null);spyDelegate Mockito.spy(new AddressService(addressDao));}public String getAddressForUser(String userName){return spyDelegate.getAddressForUser(userName);}
} 如我们所见该间谍与AddressDaoMock非常相似。 但是在这种情况下真正的bean使用构造函数注入自动关联其依赖项。 因此我们需要定义非默认构造函数并且还要进行构造函数注入。 但是我们不会将注入的依赖项传递给父构造函数。 为了启用对真实对象的监视我们将构造具有所有依赖项的新实例将其包装到Mockito间谍实例中并将其存储到spyDelegate属性中。 我们期望在测试期间调用方法getAddressForUser() 因此我们将此调用委托给spyDelegate 。 可以在测试中通过Lombok的Getter批注定义的getter访问此属性。 测试本身如下所示 ActiveProfiles({UserService-aop-test, aop})
RunWith(SpringJUnit4ClassRunner.class)
SpringApplicationConfiguration(AopApplication.class)
public class UserServiceAopITest {Autowiredprivate UserService userService;Autowiredprivate AddressService addressService;Testpublic void testGetUserDetails() {// GIVENAddressServiceSpy addressServiceSpy (AddressServiceSpy) addressService;// WHENString actualUserDetails userService.getUserDetails(john);// THEN Assert.assertEquals(User john, 3 Dark Corner, actualUserDetails);Mockito.verify(addressServiceSpy.getSpyDelegate()).getAddressForUser(john);}
} 这是非常简单的。 配置文件UserService-aop-test确保可以扫描AddressServiceSpy 。 配置文件aop在AddressLogger方面确保相同。 在自动装配测试对象UserService及其依赖项AddressService 我们知道可以将其spyDelegate为AddressServiceSpy并在调用测试方法后验证其spyDelegate属性的调用。 由Spring AOP代理的假Spring Bean 显然将调用委派给Mockito模拟或间谍会使测试复杂化。 如果我们仅需要伪造逻辑那么这些模式通常会被大刀阔斧。 在这种情况下我们可以使用此类伪造品 Primary
Repository
Profile(AddressService-aop-fake-test)
public class AddressDaoFake extends AddressDao{public String readAddress(String userName) {return userName s address;}
} 并以这种方式用于测试 ActiveProfiles({AddressService-aop-fake-test, aop})
RunWith(SpringJUnit4ClassRunner.class)
SpringApplicationConfiguration(AopApplication.class)
public class AddressServiceAopFakeITest {Autowiredprivate AddressService addressService; Testpublic void testGetAddressForUser() {// GIVEN - Spring context// WHEN String actualAddress addressService.getAddressForUser(john);// THEN Assert.assertEquals(johns address, actualAddress);}
} 我认为这个测试不需要解释。 这些示例的源代码托管在Github上。 翻译自: https://www.javacodegeeks.com/2016/01/mock-spring-bean-version-2.htmlspring-bean版本