Je viens de commencer à jouer avec Guice, et un cas d'utilisation auquel je peux penser est que dans un test, je veux juste remplacer une seule liaison. Je pense que j'aimerais utiliser le reste des liaisons de niveau de production pour m'assurer que tout est correctement configuré et pour éviter la duplication.
Alors imaginez que j'ai le module suivant
public class ProductionModule implements Module {
public void configure(Binder binder) {
binder.bind(InterfaceA.class).to(ConcreteA.class);
binder.bind(InterfaceB.class).to(ConcreteB.class);
binder.bind(InterfaceC.class).to(ConcreteC.class);
}
}
Et dans mon test, je veux seulement remplacer InterfaceC, tout en gardant InterfaceA et InterfaceB intacts, donc je voudrais quelque chose comme:
Module testModule = new Module() {
public void configure(Binder binder) {
binder.bind(InterfaceC.class).to(MockC.class);
}
};
Guice.createInjector(new ProductionModule(), testModule);
J'ai également essayé ce qui suit, sans succès:
Module testModule = new ProductionModule() {
public void configure(Binder binder) {
super.configure(binder);
binder.bind(InterfaceC.class).to(MockC.class);
}
};
Guice.createInjector(testModule);
Est-ce que quelqu'un sait s'il est possible de faire ce que je veux ou suis-je en train d'aboyer complètement le mauvais arbre ??
--- Suivi: Il semblerait que je puisse réaliser ce que je veux si j'utilise la balise @ImplementedBy sur l'interface et que je fournis simplement une liaison dans le cas de test, ce qui fonctionne bien quand il y a un mappage 1-1 entre l'interface et l'implémentation.
De plus, après en avoir discuté avec un collègue, il semblerait que nous nous dirigerions vers le remplacement d'un module entier et la garantie que nos modules sont définis correctement. Il semble que cela puisse causer un problème, cependant, lorsqu'une liaison est mal placée dans un module et doit être déplacée, ce qui peut éventuellement interrompre une charge de tests car les liaisons peuvent ne plus être disponibles pour être remplacées.
Ce n'est peut-être pas la réponse que vous cherchez, mais si vous écrivez des tests unitaires, vous ne devriez probablement pas utiliser d'injecteur et plutôt injecter des objets faux ou faux à la main.
D'un autre côté, si vous voulez vraiment remplacer une seule liaison, vous pouvez utiliser Modules.override(..)
:
public class ProductionModule implements Module {
public void configure(Binder binder) {
binder.bind(InterfaceA.class).to(ConcreteA.class);
binder.bind(InterfaceB.class).to(ConcreteB.class);
binder.bind(InterfaceC.class).to(ConcreteC.class);
}
}
public class TestModule implements Module {
public void configure(Binder binder) {
binder.bind(InterfaceC.class).to(MockC.class);
}
}
Guice.createInjector(Modules.override(new ProductionModule()).with(new TestModule()));
Voir les détails ici .
Mais comme le recommande javadoc pour Modules.overrides(..)
, vous devez concevoir vos modules de manière à ne pas avoir à remplacer les liaisons. Dans l'exemple que vous avez donné, vous pouvez accomplir cela en déplaçant la liaison de InterfaceC
vers un module séparé.
Pourquoi ne pas utiliser l'héritage? Vous pouvez remplacer vos liaisons spécifiques dans la méthode overrideMe
, en laissant les implémentations partagées dans la méthode configure
.
public class DevModule implements Module {
public void configure(Binder binder) {
binder.bind(InterfaceA.class).to(TestDevImplA.class);
overrideMe(binder);
}
protected void overrideMe(Binder binder){
binder.bind(InterfaceC.class).to(ConcreteC.class);
}
};
public class TestModule extends DevModule {
@Override
public void overrideMe(Binder binder) {
binder.bind(InterfaceC.class).to(MockC.class);
}
}
Et enfin créez votre injecteur de cette façon:
Guice.createInjector(new TestModule());
Si vous ne voulez pas changer votre module de production et si vous avez une structure de projet par défaut de type maven comme
src/test/Java/...
src/main/Java/...
Vous pouvez simplement créer une nouvelle classe ConcreteC
dans votre répertoire de test en utilisant le même package que pour votre classe d'origine. Guice liera alors InterfaceC
à ConcreteC
depuis votre répertoire de test tandis que toutes les autres interfaces seront liées à vos classes de production.
Vous souhaitez utiliser Juckito où vous pouvez déclarer votre configuration personnalisée pour chaque classe de test.
@RunWith(JukitoRunner.class)
class LogicTest {
public static class Module extends JukitoModule {
@Override
protected void configureTest() {
bind(InterfaceC.class).to(MockC.class);
}
}
@Inject
private InterfaceC logic;
@Test
public testLogicUsingMock() {
logic.foo();
}
}
Dans une configuration différente, nous avons plus d'une activité définie dans des modules séparés. L'activité injectée se trouve dans un module de bibliothèque Android, avec sa propre définition de module RoboGuice dans le fichier AndroidManifest.xml.
La configuration ressemble à ceci. Dans le module de bibliothèque, il y a ces définitions:
AndroidManifest.xml:
<application Android:allowBackup="true">
<activity Android:name="com.example.SomeActivity/>
<meta-data
Android:name="roboguice.modules"
Android:value="com.example.MainModule" />
</application>
Ensuite, nous avons un type en cours d'injection:
interface Foo { }
Quelques implémentations par défaut de Foo:
class FooThing implements Foo { }
MainModule configure l'implémentation FooThing pour Foo:
public class MainModule extends AbstractModule {
@Override
protected void configure() {
bind(Foo.class).to(FooThing.class);
}
}
Et enfin, une activité qui consomme Foo:
public class SomeActivity extends RoboActivity {
@Inject
private Foo foo;
}
Dans le module d'application Android Android, nous aimerions utiliser SomeActivity
mais, à des fins de test, injecter notre propre Foo
.
public class SomeOtherActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
Intent intent = new Intent(this, SomeActivity.class);
startActivity(intent);
}
}
On pourrait argumenter pour exposer la gestion du module à l'application cliente, cependant, nous devons principalement cacher les composants injectés car le module de bibliothèque est un SDK, et exposer des morceaux a des implications plus importantes.
(Rappelez-vous, ceci est pour les tests, nous connaissons donc les composants internes de SomeActivity, et nous savons qu'il consomme un (paquet visible) Foo).
La façon dont j'ai trouvé que ça fonctionnait est logique; utilisez le remplacement suggéré pour test:
public class SomeOtherActivity extends Activity {
private class OverrideModule
extends AbstractModule {
@Override
protected void configure() {
bind(Foo.class).to(OtherFooThing.class);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RoboGuice.overrideApplicationInjector(
getApplication(),
RoboGuice.newDefaultRoboModule(getApplication()),
Modules
.override(new MainModule())
.with(new OverrideModule()));
}
@Override
protected void onResume() {
super.onResume();
Intent intent = new Intent(this, SomeActivity.class);
startActivity(intent);
}
}
Maintenant, lorsque SomeActivity
est démarré, il obtiendra OtherFooThing
pour son instance Foo
injectée.
C'est une situation très spécifique où, dans notre cas, OtherFooThing a été utilisé en interne pour enregistrer des situations de test, tandis que FooThing a été utilisé, par défaut, pour toutes les autres utilisations.
Gardez à l'esprit, nous sommes en utilisant #newDefaultRoboModule
dans nos tests unitaires, et cela fonctionne parfaitement.