Pour tout projet Guice complexe, vous devez ajouter des tests pour vous assurer que les modules peuvent être utilisés pour créer vos classes. Dans votre exemple, si B était un type que Guice ne pouvait pas comprendre comment créer, alors Guice ne sera pas capable de créer A. Si A n'était pas nécessaire pour démarrer le serveur, mais était nécessaire lorsque votre serveur gérait un demande, cela causerait des problèmes.
Dans mes projets, j'écris des tests pour des modules non triviaux. Pour chaque module, j'utilise requireBinding() pour déclarer quelles liaisons le module requiert mais ne définit pas. Dans mes tests, je crée un injecteur Guice en utilisant le module testé et un autre module qui fournit les liaisons requises. Voici un exemple utilisant JUnit4 et JMock:
/** Module that provides LoginService */
public class LoginServiceModule extends AbstractModule {
@Override
protected void configure() {
requireBinding(UserDao.class);
}
@Provides
LoginService provideLoginService(UserDao dao) {
...
}
}
@RunWith(JMock.class)
public class LoginServiceModuleTest {
private final Mockery context = new Mockery();
@Test
public void testModule() {
Injector injector = Guice.createInjector(
new LoginServiceModule(), new ModuleDeps());
// next line will throw an exception if dependencies missing
injector.getProvider(LoginService.class);
}
private class ModuleDeps extends AbstractModule {
private final UserDao fakeUserDao;
public ModuleDeps() {
fakeUserDao = context.mock(UserDao.class);
}
@Override
protected void configure() {}
@Provides
Server provideUserDao() {
return fakeUserDao;
}
}
}
Notez que le test ne demande qu'un fournisseur. C'est suffisant pour déterminer que Guice pourrait résoudre les liaisons. Si LoginService a été créé par une méthode fournisseur, ce test ne testera pas le code dans la méthode fournisseur.
Ce test ne vérifie pas non plus que vous avez lié la bonne chose à UserDao
, ou que UserDao
a été correctement défini. Certains diront que ce genre de choses vaut rarement la peine d'être vérifié; S'il y a un problème, cela arrive une fois. Vous devriez "tester jusqu'à ce que la peur se transforme en ennui". Je trouve les tests Module utiles parce que j'ajoute souvent de nouveaux points d'injection, et il est facile d'oublier d'ajouter une liaison.
Les appels requireBinding()
peuvent aider Guice à intercepter les liaisons manquantes avant qu'il ne renvoie votre injecteur! Dans l'exemple ci-dessus, le test fonctionnerait toujours si les appels requireBinding()
n'existaient pas, mais j'aime les avoir parce qu'ils servent de documentation. Pour les modules plus compliqués (comme mon module racine), je peux utiliser Modules.override() pour remplacer les liaisons que je ne veux pas au moment du test (par exemple, si je veux vérifier que mon objet racine doit être créé, je vais probablement ne le veux pas créer un objet qui se connectera à la base de données). Pour les projets simples, vous pouvez uniquement tester le module de niveau supérieur.
Notez que Guice will not inject nulls à moins que le champ ne soit annoté avec @Nullable
de sorte que vous ayez très rarement besoin de vérifier que les objets injectés sont non nuls dans vos tests.En fait, quand j'annote des constructeurs avec @Inject
je ne cherche pas à vérifier si les paramètres sont null
(en fait, mes tests injectent souvent null
dans le constructeur pour garder les tests simples).
Merci beaucoup. Vos arguments sont très clairs. J'ai des dépendances qui ne sont pas créées après que l'application a été déployée – yeraycaballero
'mes tests injectent souvent une valeur nulle dans le constructeur pour garder les tests simples' => cela peut indiquer que votre classe n'est pas aussi cohérente – beluchin
@beluchin pourriez-vous expliquer ce que vous voulez dire? J'essaie d'éviter de faire du «vrai travail» dans mon constructeur, donc passer dans un «null» dans le constructeur est rarement un problème. Si je teste une classe, et que la ou les méthodes que je teste n'utilisent pas l'un des champs, le fait de passer un 'null' pour ce paramètre constructeur est la chose la plus simple à faire. Si cela ne marche pas, j'injecte un objet simulé (pour les services) ou une instance réelle (pour les objets de valeur) mais l'un ou l'autre rend le code plus compliqué que si je passais juste dans 'null' – NamshubWriter