I'm having an issue when trying to mock a property of a service from within a Junit test:
@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 has no effect since the the result is not the expected. Here is the code of both the service and the 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";
}
}
As we can see the actual service is meant to return "foo", but the test mocks the dao so the service returns "var". I know it's a CGLIB proxy related thing but I can't figure out how to make it work without using a setter for the fooDao property. Any help would be appreciated.
Regards and thanks in advance.
You have to unwrap the proxy and set the field on the target object:
ReflectionTestUtils.setField(unwrapFooService(), "fooDao", mockFooDao);
The unwrapFooService()
can be defined as follows:
private FooServiceImpl unwrapFooService() {
if(AopUtils.isAopProxy(fooService) && fooService instanceof Advised) {
Object target = ((Advised) fooService).getTargetSource().getTarget();
return (FooServiceImpl)target;
}
return null;
}
The problem is quite complex, but solvable. As you have guessed this is a side-effect of CGLIB proxies being used. In principle, Spring creates a subclass of your FooServiceImpl
named similar to FooServiceImpl$EnhancerByCGLIB
. This subclass contains a reference to the original FooServiceImpl
as well as... all the fields FooServiceImpl
has (which is understandable - this is a subclass).
So there are actually two variables: FooServiceImpl$EnhancerByCGLIB.fooDao
and FooServiceImpl.fooDao
. You are assigning a mock to the former but your service uses the latter... I wrote about this pitfalls some time ago.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With