[SPRING] 트랜잭션 주석은 서비스가 조롱되는 것을 방지합니다.
SPRING트랜잭션 주석은 서비스가 조롱되는 것을 방지합니다.
규칙에 서비스 클래스를 사용하는 drools 규칙 파일이 있습니다. 그래서 하나의 규칙은 다음과 같이합니다 :
eval (countryService.getCountryById (1)! = null)
@service와 @Transactional (propagation = Propagation.SUPPORTS)으로 주석 된 유효성 검사 서비스에서, drools 파일은 statelessKnowledgebase에 사용되며, 잠김에서 사용해야하는 사실이 추가됩니다. 일단 이것이 끝나면 session.execute (사실)가 호출되고 룰 엔진이 시작됩니다.
규칙을 테스트하기 위해 countryService.getCountryById ()를 스텁하고 싶습니다. mockito를 사용하면 큰 문제는 없습니다. drools 설정을 사용하는 다른 서비스에도이 작업을 수행하면 정상적으로 작동합니다. 그러나이 특별한 경우 countryService는 스터브되지 않았으며 이유를 파악할 수 없었습니다. 많은 시간을 보내고 내 코드를 확인한 후에 서비스 위의 @Transactional을 사용하거나이 주석이 부족하다는 점을 발견했습니다. @Transaction이 없다면 mockito가 문제없이 countryservice를 모의하게 만들었고, @transactional을 제자리에두면 mockito가 원래의 countryservice 객체가 사용되도록 mockito를 삽입하는 데 오류나 힌트없이 실패하게되었습니다.
내 질문은이 주석으로 인해이 문제가 발생하는 이유입니다. @Transactional이 설정되었을 때 mockito가 mockito를 삽입 할 수없는 이유는 무엇입니까? moockito가 디버깅 할 때 countryService를 검사 할 때 실패한 것으로 나타났습니다. 내 debugwindow에서 countryservice를 검사 할 때 drools 세션에 전역으로 추가 될 때 다음과 같은 차이가 있습니다.
또한 @transactional과 함께 countryservice 메서드의 내 중단 점 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);
}
그리고 테스트 클래스 :
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);
}
이 @transactional 주석을 유지하고 countryservice 메소드를 스텁 (stub) 할 수 있다면 어떤 생각을해야할까요?
문안 인사,
남자 이름
해결법
-
==============================
1.무슨 일이 일어나는가 ValidationService가 JdkDynamicAopProxy에 래핑되고 있기 때문에 Mockito가 서비스에 mockito를 삽입하려고 할 때이를 주입 할 필드가 보이지 않습니다. 다음 두 가지 중 하나를 수행해야합니다.
무슨 일이 일어나는가 ValidationService가 JdkDynamicAopProxy에 래핑되고 있기 때문에 Mockito가 서비스에 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; }
문제에 대한 블로그 항목
-
==============================
2.ReflectionTestUtils는 Spring 4.3.1부터 프록시를 자동으로 unwrap해야한다는 점에 유의하십시오. 그래서
ReflectionTestUtils는 Spring 4.3.1부터 프록시를 자동으로 unwrap해야한다는 점에 유의하십시오. 그래서
ReflectionTestUtils.setField(validationService, "countryService", countryService);
귀하의 countryService에 @Transactional, @Cacheable ... (즉, 런타임시 프록시 뒤에 숨겨 짐) 주석이 달린 경우에도 작동해야합니다.
관련 문제 : SPR-14050
-
==============================
3.SuperSaiyen의 답을 바탕으로, 나는 더 간단하고 안전한 타입을위한 drop-in 유틸리티 클래스를 만들었습니다 :
SuperSaiyen의 답을 바탕으로, 나는 더 간단하고 안전한 타입을위한 drop-in 유틸리티 클래스를 만들었습니다 :
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; } }
사용법은 간단합니다. 테스트 메소드를 시작할 때 mockFieldOnBean (Object beanToInjectMock, Class
classToMock) 메소드를 mock을 삽입하려는 bean과 조롱해야하는 객체의 클래스로 호출하면됩니다. 예: SomeOtherService의 Autowired Bean을 보유하고있는 SomeService 타입의 Bean을 가지고 있다고 가정 해 봅시다.
@Component public class SomeService { @Autowired private SomeOtherService someOtherService; // some other stuff }
SomeService bean에서 someOtherService를 조롱하려면 다음을 사용하십시오.
@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(); } }
모든 것이 제대로 작동해야합니다.
-
==============================
4.또 다른 해결책은 스프링이 모든 것을 연결하기 전에 모의 객체를 Spring 컨텍스트에 추가하는 것입니다. 그러면 테스트가 시작되기 전에 이미 삽입되어있을 것입니다. 수정 된 테스트는 다음과 같이 보일 수 있습니다.
또 다른 해결책은 스프링이 모든 것을 연결하기 전에 모의 객체를 Spring 컨텍스트에 추가하는 것입니다. 그러면 테스트가 시작되기 전에 이미 삽입되어있을 것입니다. 수정 된 테스트는 다음과 같이 보일 수 있습니다.
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { Application.class, MockConfiguration.class }) public class TestForeignAddressPostalCode extends BaseTestDomainIntegration { public static class MockConfiguration { @Bean @Primary public CountryService mockCountryService() { return mock(CountryService.class); } } @Autowired protected CountryService mockCountryService; @Autowired private ValidationService level2ValidationService; @BeforeMethod(alwaysRun=true) protected void setup() { // set up you mock stubs here // ...
@Primary 주석은 중요합니다. 새 모의 CountryService가 정상적인 주입을 대체하여 주입에 최우선 순위를 부여해야합니다. 그러나 클래스가 여러 위치에 주입되는 경우 의도하지 않은 부작용이있을 수 있습니다.
from https://stackoverflow.com/questions/12857981/transactional-annotation-avoids-services-being-mocked by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] org.aspectj.weaver.reflect.ReflectionWorld를 찾을 수 없습니다. (0) | 2019.01.05 |
---|---|
[SPRING] 스프링 부트 시작 시간 단축 (0) | 2019.01.05 |
[SPRING] 봄에 jndi에 선언적으로 객체를 바인딩하는 법? (0) | 2019.01.05 |
[SPRING] 스프링 데이터 jpa @transactional (0) | 2019.01.04 |
[SPRING] `springSecurityFilterChain` 빈은 어디에 정의해야합니까? (0) | 2019.01.04 |