Mocking a property of a CGLIB proxied service not working
Asked Answered
W

1

18

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.

Wendt answered 27/1, 2012 at 13:24 Comment(0)
S
42

Short answer

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;
}

...long one

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.

Sinew answered 27/1, 2012 at 13:35 Comment(4)
@frandiaz83: glad I could help! Consider accepting and/or upvoting correct answer to point future readers to the proper solution.Sinew
Yes, of course. I've accepted the answer but I can't vote up since I don't have enough reputation... Thanks again.Wendt
There were some problems with unwrapFooService(): unresolved name a and missing return in case the condition is not true. I took the liberty to edit it so that it compiles & works (for me). Also, +1Magnetoelectricity
No need to write unwrapFooService yourself anymore with Spring 4.2. Check out https://mcmap.net/q/480377/-is-it-possible-to-unproxy-a-spring-beanCrummy

© 2022 - 2024 — McMap. All rights reserved.