Mic*_*ael 8 testng spring transactional mockito
我有一个drools规则文件,它使用规则中的服务类.所以一条规则做了这样的事情:
eval(countryService.getCountryById(1)!= null)
在使用@service和@Transactional(propagation = Propagation.SUPPORTS)注释的验证服务中,drools文件用于statelessKnowledgebase,并添加应在流氓中使用的事实.完成后,调用session.execute(fact)并启动规则引擎.
为了测试规则,我想将countryService.getCountryById()存根.使用mockito没什么大问题.对于使用drools设置的其他服务也做了这个,它工作正常.然而,在这种特殊情况下,countryService没有存根,我无法弄清楚原因.花了很多时间并检查我的代码后,我发现在服务之上使用@Transactional或缺少这个注释会产生差异.缺少@Transaction使mockito模拟了countryservice没有任何问题,让@transactional到位导致mockito失败(没有任何错误或提示)注入模拟以便使用原始的countryservice对象.
我的问题是为什么这个注释会导致这个问题.为什么在设置@Transactional时无法模拟注入模拟?我注意到mockito失败了,因为我在调试和检查countryService时将它作为全局添加到drools会话中当我在debugwindow中检查countryservice时,我看到以下区别:
与@transactional:countryService的值为CountryService $$ EnhancerByCGLIB $$ b80dbb7b
没有@transactional:countryService的值为CountryService $$ EnhancerByMockitoWithCGLIB $$ 27f34dc1
除了@transactional之外,我在乡村服务方法中发现断点getCountryById并且调试器在该断点处停止,但是没有@transactional我的断点被跳过,因为mockito会绕过它.
ValidationService:
@Service
@Transactional(propagation=Propagation.SUPPORTS)
public class ValidationService
{
@Autowired
private CountryService countryService;
public void validateFields(Collection<Object> facts)
{
KnowledgeBase knowledgeBase = (KnowledgeBase)AppContext.getApplicationContext().getBean(knowledgeBaseName);
StatelessKnowledgeSession session = knowledgeBase.newStatelessKnowledgeSession();
session.setGlobal("countryService", countryService);
session.execute(facts);
}
Run Code Online (Sandbox Code Playgroud)
和测试类:
public class TestForeignAddressPostalCode extends BaseTestDomainIntegration
{
private final Collection<Object> postalCodeMinLength0 = new ArrayList<Object>();
@Mock
protected CountryService countryService;
@InjectMocks
private ValidationService level2ValidationService;
@BeforeMethod(alwaysRun=true)
protected void setup()
{
// Get the object under test (here the determination engine)
level2ValidationService = (ValidationService) getAppContext().getBean("validationService");
// and replace the services as documented above.
MockitoAnnotations.initMocks(this);
ForeignAddress foreignAddress = new ForeignAddress();
foreignAddress.setCountryCode("7029");
foreignAddress.setForeignPostalCode("foreign");
// mock country to be able to return a fixed id
Country country = mock(Country.class);
foreignAddress.setLand(country);
doReturn(Integer.valueOf(1)).when(country).getId();
doReturn(country).when(countryService).getCountryById(anyInt());
ContextualAddressBean context = new ContextualAddressBean(foreignAddress, "", AddressContext.CORRESPONDENCE_ADDRESS);
postalCodeMinLength0.add(context);
}
@Test
public void PostalCodeMinLength0_ExpectError()
{
// Execute
level2ValidationService.validateFields(postalCodeMinLength0, null);
}
Run Code Online (Sandbox Code Playgroud)
如果我想保留这个@transactional注释但是也能够存根乡村服务方法,那么该怎么办?
问候,
迈克尔
发生的事情是您的ValidationService被包装在JdkDynamicAopProxy中,因此当Mockito将模拟注入服务时,它不会看到任何字段将它们注入.你需要做两件事之一:
代码示例:
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
ValidationService validationService = (ValidationService) unwrapProxy(level2ValidationService);
ReflectionTestUtils.setField(validationService, "countryService", countryService);
}
public static final Object unwrapProxy(Object bean) throws Exception {
/*
* If the given object is a proxy, set the return value as the object
* being proxied, otherwise return the given object.
*/
if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
Advised advised = (Advised) bean;
bean = advised.getTargetSource().getTarget();
}
return bean;
}
Run Code Online (Sandbox Code Playgroud)
小智 8
请注意,自Spring 4.3.1起,ReflectionTestUtils应自动解包代理.所以
ReflectionTestUtils.setField(validationService, "countryService", countryService);
Run Code Online (Sandbox Code Playgroud)
现在应该工作,即使你countryService
的注释是@Transactional
,@Cacheable
...(也就是说,在运行时隐藏在代理后面)
相关问题:SPR-14050
根据SuperSaiyen 的回答,我创建了一个嵌入式实用程序类,使其更简单且类型安全:
import org.mockito.Mockito;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.test.util.ReflectionTestUtils;
@SuppressWarnings("unchecked")
public class SpringBeanMockUtil {
/**
* If the given object is a proxy, set the return value as the object being proxied, otherwise return the given
* object.
*/
private static <T> T unwrapProxy(T bean) {
try {
if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
Advised advised = (Advised) bean;
bean = (T) advised.getTargetSource().getTarget();
}
return bean;
}
catch (Exception e) {
throw new RuntimeException("Could not unwrap proxy!", e);
}
}
public static <T> T mockFieldOnBean(Object beanToInjectMock, Class<T> classToMock) {
T mocked = Mockito.mock(classToMock);
ReflectionTestUtils.setField(unwrapProxy(beanToInjectMock), null, mocked, classToMock);
return mocked;
}
}
Run Code Online (Sandbox Code Playgroud)
用法很简单,只需在测试方法的开头,mockFieldOnBean(Object beanToInjectMock, Class<T> classToMock)
使用要注入模拟的 bean 和应模拟的对象的类调用该方法。例子:
假设您有一个类型为 的 bean,SomeService
该 bean 包含一个自动装配的 bean SomeOtherService
,例如;
@Component
public class SomeService {
@Autowired
private SomeOtherService someOtherService;
// some other stuff
}
Run Code Online (Sandbox Code Playgroud)
嘲笑someOtherService
的SomeService
豆,使用以下命令:
@RunWith(SpringJUnit4ClassRunner.class)
public class TestClass {
@Autowired
private SomeService someService;
@Test
public void sampleTest() throws Exception {
SomeOtherService someOtherServiceMock = SpringBeanMockUtil.mockFieldOnBean(someService, SomeOtherService.class);
doNothing().when(someOtherServiceMock).someMethod();
// some test method(s)
verify(someOtherServiceMock).someMethod();
}
}
Run Code Online (Sandbox Code Playgroud)
一切都应该像他们应该的那样工作。
归档时间: |
|
查看次数: |
6089 次 |
最近记录: |