Se moquer d'une propriété d'un service proxé CGLIB ne fonctionne pas
Question
J'ai un problème pour essayer de se moquer d'une propriété d'un service à partir d'un test 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 n'a aucun effet puisque le résultat n'est pas attendu. Voici le code du service et du 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";
}
}
Comme nous pouvons le voir, le service réel est destiné à retourner "foo", mais le test se moque du DAO pour que le service renvoie "var". Je sais que c'est une chose liée au proxy CGLIB, mais je ne peux pas comprendre comment le faire fonctionner sans utiliser de setter pour la propriété Foodao. Toute aide serait appréciée.
Cordialement et merci d'avance.
La solution
Réponse courte
Vous devez déballer le proxy et définissez le champ sur l'objet cible:
ReflectionTestUtils.setField(unwrapFooService(), "fooDao", mockFooDao);
La unwrapFooService()
peut être défini comme suit:
private FooServiceImpl unwrapFooService() {
if(AopUtils.isAopProxy(fooService) && fooService instanceof Advised) {
Object target = ((Advised) fooService).getTargetSource().getTarget();
return (FooServiceImpl)target;
}
return null;
}
...une longue
Le problème est assez complexe, mais résoluble. Comme vous l'avez deviné, il s'agit d'un effet secondaire des proxys CGLIB utilisés. En principe, le printemps crée une sous-classe de votre FooServiceImpl
nommé similaire à FooServiceImpl$EnhancerByCGLIB
. Cette sous-classe contient un référence à l'original FooServiceImpl
ainsi que ... tous les champs FooServiceImpl
a (ce qui est compréhensible - il s'agit d'une sous-classe).
Il y a donc deux variables: FooServiceImpl$EnhancerByCGLIB.fooDao
et FooServiceImpl.fooDao
. Vous attribuez une simulation au premier mais votre service utilise ce dernier ... je a écrit À propos de ces pièges il y a quelque temps.