Издевательствовать свойство проксированного сервиса CGLIB не работает
У меня возникла проблема при попытке издеваться над свойством службы из теста Junit:
@ContextConfiguration("classpath:application-config.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class FooServiceTests {
@Autowired
private FooServiceImpl fooService;
@Test
public void testFoo() {
String str = fooService.foo();
assertEquals("Var", str);
}
@Before
public void mockFooDao() throws Exception {
FooDao mockFooDao = Mockito.mock(FooDao.class);
Mockito.when(mockFooDao.foo()).thenReturn("Var");
ReflectionTestUtils.setField(fooService, "fooDao", mockFooDao);
}
}
Mocking fooDao не влияет, поскольку результат не является ожидаемым. Вот код и сервиса, и dao:
@Service("fooService")
public class FooServiceImpl implements FooService {
@Autowired
protected FooDao fooDao;
@Override
public String foo() {
return fooDao.foo();
}
}
@Repository
public class FooDaoImpl implements FooDao {
@Override
public String foo() {
return "foo";
}
}
Как мы видим, фактическая служба предназначена для возврата "foo", но тест издевается над dao, поэтому служба возвращает "var". Я знаю, что это связано с прокси-сервером CGLIB, но я не могу понять, как заставить его работать, не используя setter для свойства fooDao. Любая помощь будет оценена.
С уважением и благодарностью заранее.
Ответы
Ответ 1
Короткий ответ
Вы должны развернуть прокси и установить поле на целевом объекте:
ReflectionTestUtils.setField(unwrapFooService(), "fooDao", mockFooDao);
unwrapFooService()
можно определить следующим образом:
private FooServiceImpl unwrapFooService() {
if(AopUtils.isAopProxy(fooService) && fooService instanceof Advised) {
Object target = ((Advised) fooService).getTargetSource().getTarget();
return (FooServiceImpl)target;
}
return null;
}
... long one
Проблема довольно сложная, но разрешимая. Как вы уже догадались, это побочный эффект использования прокси-серверов CGLIB. В принципе, Spring создает подкласс вашего FooServiceImpl
с именем, похожим на FooServiceImpl$EnhancerByCGLIB
. Этот подкласс содержит ссылку на исходный FooServiceImpl
, а также... все поля FooServiceImpl
имеют (что понятно - это подкласс).
Таким образом, на самом деле существуют две переменные: FooServiceImpl$EnhancerByCGLIB.fooDao
и FooServiceImpl.fooDao
. Вы назначаете макет первому, но ваша служба использует последнее... Я написал об этой подводной камне некоторое время назад.