web-dev-qa-db-fra.com

Injecteur Guice dans les tests JUnit

En utilisant Guice, est-ce une bonne pratique pour obtenir un nouvel injecteur dans chaque classe de test JUnit, car chaque classe de test doit être indépendante?

19
Alexis Dufrenoy

Jetez un oeil à Guice Berry .

Je ne recommanderai pas de l'utiliser maintenant (la documentation est vraiment terrible), mais en regardant leur approche peut vous faire penser clairement à la façon dont DI devrait être fait dans jUnit.

5
Sotomajor

Vous devez vraiment éviter d'utiliser Guice dans les tests unitaires car chaque test doit être suffisamment petit pour que l'ID manuelle soit gérable. En utilisant Guice (ou n'importe quel DI) dans les tests unitaires, vous cachez un avertissement que votre classe devient trop grande et assume trop de responsabilités.

Pour tester le code d'amorçage et les tests d'intégration, alors oui, créez un injecteur différent pour chaque test.

34

Au cas où quelqu'un tomberait sur cette question et voudrait voir comment faire fonctionner les annotations Guice à partir des tests unitaires, étendez vos tests à partir d'une classe de base comme celle ci-dessous et appelez injector.injectMembers(this);

public class TestBase {
    protected Injector injector = Guice.createInjector(new AbstractModule() {
        @Override
        protected void configure() {
            bind(HelloService.class);
        }
    });

    @Before
    public void setup () {
        injector.injectMembers(this);
    }
}

Ensuite, votre test peut obtenir un HelloService injecté comme celui-ci

public class HelloServiceTest extends TestBase {
    @Inject
    HelloService service;

    @Test
    public void testService() throws Exception {
       //Do testing here
    }
}
26
Joe Abrams

Je pense que l'utilisation de DI rendra le code de test unitaire plus simple, j'utilise toujours DI pour le test unitaire et aussi pour le test d'intégration.

Sans DI, tout est difficile à coder. Soit en utilisant Guice Inject or Spring Autowired. comme mon code de test ci-dessous:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/application-context.xml")
public class When_inexists_user_disabled {
    @Autowired
    IRegistrationService registrationService;

    private int userId;

    @Before
    public void setUp() {
        Logger.getRootLogger().setLevel(Level.INFO);
        Logger.getLogger("org.springframework").setLevel(Level.WARN);
        BasicConfigurator.configure();

        userId = 999;
    }

    @Test(expected=UserNotFoundException.class)
    public void user_should_have_disabled() throws UserNotFoundException {
        registrationService.disable(userId);
    }

}
10
Adi Sembiring

Cela dépend de la version de JUnit utilisée. Nos équipes ont utilisé Junit4 avec succès et étudient maintenant JUnit5.

Dans Junit5, nous utilisons des extensions.

    public class InjectionPoint implements BeforeTestExecutionCallback {

        @Override
        public void beforeTestExecution(ExtensionContext context) throws Exception {

            List<Module> modules = Lists.newArrayList(new ConfigurationModule());

            Optional<Object> test = context.getTestInstance();

            if (test.isPresent()) {
                RequiresInjection requiresInjection = test.get().getClass().getAnnotation(RequiresInjection.class);

                if (requiresInjection != null) {
                    for (Class c : requiresInjection.values()) {
                        modules.add((Module) c.newInstance());
                    }
                }

                Module aggregate = Modules.combine(modules);
                Injector injector = Guice.createInjector(aggregate);

                injector.injectMembers(test.get());
                getStore(context).put(injector.getClass(), injector);
            }

        }

        private Store getStore(ExtensionContext context) {
            return context.getStore(Namespace.create(getClass()));
        }

    }

Ensuite, chaque test utilise l'annotation RequirementsInjection, qui peut accepter un tableau de modules internes à agréger, ou aucun pour utiliser la valeur par défaut.

    @RequiresInjection
    public class Junit5InjectWithoutModuleTest {

        @Inject
        private TestEnvironment environment;

        @Test
        public void shouldAccessFromOuterModule() {
            assertThat(environment).isNotNull();
        }

    }

Et voici l'annotation:

    @ExtendWith(InjectionPoint.class)
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
    public @interface RequiresInjection {

        Class<? extends Module>[] values() default {};

    }

JUnit5 est encore nouveau pour moi, donc je regarde peut-être des modèles, mais jusqu'à présent, les extensions semblent faire l'affaire.

Avec JUnit4, nous utilisons une approche similaire, sauf que l'injection a lieu dans la méthode createTest de notre lanceur de test personnalisé, puis chaque test implémente une interface RequirementsInjection qui a une méthode "getModule".

Je devrais probablement aussi donner un coup de pouce à TestNG, car le support de Guice est intégré. L'utilisation est aussi simple que ceci:

@Guice({SomeObjectModule.class})
    public class MyTest {

        @Inject
        SomeObject someObject;    

    }
4
Chris S.

J'ai trouvé AtUnit pour être un excellent complément à Guice (il traite même de l'intégration du framework simulé).

Cela rend les classes de tests unitaires extrêmement claires et concises (n'y voyez jamais de Injector) et, le cas échéant, vous permet également d'exercer vos liaisons de production dans le cadre de vos tests unitaires.

2
sxc731

Je suggère ce cadre que j'ai récemment écrit Guice-Behave .

C'est très simple, avec deux annotations vous pouvez exécuter le test dans le même contexte de votre application.

Vous pouvez définir vos mocks à l'intérieur du module Guice et de cette façon il est très facile de les réutiliser.

2
Alessandro