4

I'm working on a test automation framework which use TestNG. I decided to use Dependency Injection pattern in order to implement more readable, reusable page objects and tests.

I've chosen Google Guice due to TestNG provides built-in support to inject test objects with Guice Modules. I only had to specify my Guice Modules as you can see at next code snippet:

    @Guice(modules = CommModule.class)
    public class CommunicationTest {

        @Inject
        private Communication comms;

        @Test
        public void testSendMessage() {
            Assertions.assertThat(comms.sendMessage("Hello World!")).isTrue();
        }
    }

So far so good, although I'm going to need more advance DI features such as:

  • Lifecycle management
  • Configuration to field mapping
  • Generic binding annotations

Therefore, I'd like to use Netflix/Governator since it enhance Google Guice with these features. In order to trigger Governator features I must create the Injector through it instead of TestNG. e.g:

    Injector injector = LifecycleInjector.builder()
        .withModules(CommModules.class).build().createInjector();

And I'd like to do it mostly transparent as possible like TestNG does it.

I would like to know if:

  • Is it possible to provide my own Injector instance to TestNG in order to reuse @Guice annotation approach ?
  • Do you know any library for integrating Governator with TestNG ?

You can find in here what I've done so far.

eHayik
  • 2,981
  • 1
  • 21
  • 33

2 Answers2

3

This was not possible until now. I have fixed this in the latest snapshot version of TestNG. It should be available in the upcoming version of TestNG (Any version greater than 7.0.0)

The issue that I created to track this : https://github.com/cbeust/testng/issues/2199

In a nutshell, you can do the following :

  • Implement the interface org.testng.IInjectorFactory
  • Plugin the fully qualified class name of the newly created implementation via the command line argument -dependencyinjectorfactory
Krishnan Mahadevan
  • 14,121
  • 6
  • 34
  • 66
  • Could it be possible to specify the implementation for `org.testng.IInjectorFactory` using *Java SPI* – eHayik Dec 03 '19 at 07:50
  • @EduardoEljaiek - From an implementation perspective, yes it can be supported. But from semantics perspective, I think it won't be correct. Because if your test project depends on two other projects both of which inject their own `IInjectorFactory` variants, then TestNG would get confused as to which implementation to use because the _Java SPI_ aggregates all implementations and provides it to the user. – Krishnan Mahadevan Dec 03 '19 at 08:56
  • Ok, thanks for your comment. I'm going to post an answer showing up what I've done so far to support *Governator Injector* in my projects, meanwhile a new *TestNG* version is released. Because I like how *Java SPI* helped me out to provide an abstraction layer for my projects. It will be great if you can take a look to it in your free time. I'll appreciate your feedback. – eHayik Dec 03 '19 at 13:21
  • How do I make this work when running via maven surefire plugin? I tried to add dependencyinjectorfactorymy.injectorfactory.fqcn to the properties of maven surefire plugin, but it didn't work. I also tried running "mvn test -Ddependencyinjectorfactory=my.injectorfactory.fqcn" but it didn't work either. I could only make it work when using intelliJ idea's runner – Ubeogesh Dec 18 '19 at 07:23
  • @Ubeogesh Is `fqcn` a class or a package name ? – eHayik Dec 27 '19 at 10:10
  • @EduardoEljaiek factory's fully qualified **class** name – Ubeogesh Dec 27 '19 at 11:17
1

Since Allow user to provide DI Injector TestNG feature is going to be present in versions greater than 7.0.0. I implemented a solution using TestNG version 7.0.0 listeners.

Firstly, I created a module called autopilot-testng-runner with the following dependencies:

<dependencies>
   <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
    </dependency>

    <dependency>
        <groupId>com.google.inject</groupId>
        <artifactId>guice</artifactId>
    </dependency>

    <dependency>
        <groupId>com.netflix.governator</groupId>
        <artifactId>governator</artifactId>
    </dependency>

    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
    </dependency>

    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
    </dependency>
</dependencies> 

This module contains the artifacts described at next:

  • @AutopilotTest: Custom annotation for declaring which Guice modules must be used for creating the Injector with LifecycleInjector.builder(). I couldn't reuse @Guice annotation due to TestNG also will create its Injector and declared dependencies will be created twice.

  • AutopilotSuiteListener: Implementation of ISuiteListener for creating the parent Injector, Governator's LifecycleManager instance and bind configuration properties before Suite starts. Therefore each Suite is going to have a parent Injector built with Governator and a life-cycle manager.

  • AutopilotTestListener: ITestListener implementation in charge of injecting dependencies in the running test case.

  • META-INF/services/org.testng.ITestNGListener: Service provider configuration file containing the fully qualified names of both ITestNGListener implementations.

Then, I added autopilot-testng-runner as a maven dependency in my project

    <dependency>
        <groupId>com.github.eljaiek.playground</groupId>
        <artifactId>autopilot-testng-runner</artifactId>
        <version>${project.version}</version>
        <scope>test</scope>
    </dependency>

And finally, I replaced @Guice annotation with @AutopilotTest

    @AutopilotTest(modules = CommModule.class)
    public class CommunicationTest {

        @Inject
        private Communication comms;

        @Test
        public void testSendMessage() {
            Assertions.assertThat(comms.sendMessage("Hello World!")).isTrue();
        }
    }
eHayik
  • 2,981
  • 1
  • 21
  • 33