TestNG supports two different kinds of dependency injection: native
(performed by TestNG itself) and external (performed by a dependency
injection framework such as Guice).
TestNG lets you declare additional parameters in your methods. When
this happens, TestNG will automatically fill these parameters with the
right value. Dependency injection can be used in the following places:
-
Any @Before method or @Test method can declare a parameter of type ITestContext.
-
Any @AfterMethod method can declare a parameter of type ITestResult, which will reflect the result of the test method that was just run.
-
Any @Before and @After methods can declare a parameter of type XmlTest, which contain the current <test> tag.
-
Any @BeforeMethod (and @AfterMethod) can declare a parameter of type
java.lang.reflect.Method. This parameter will receive the
test method that will be called once this @BeforeMethod finishes (or
after the method as run for @AfterMethod).
-
Any @BeforeMethod can declare a parameter of type Object[].
This parameter will receive the list of parameters that are about to be
fed to the upcoming test method, which could be either injected by
TestNG, such as java.lang.reflect.Method or come from a @DataProvider.
-
Any @DataProvider can declare a parameter of type
ITestContext or java.lang.reflect.Method. The
latter parameter will receive the test method that is about to be invoked.
You can turn off injection with the
@NoInjection annotation:
public class NoInjectionTest { |
@DataProvider (name = "provider" ) |
public Object[][] provide() throws Exception { |
return new Object[][] { { CC. class .getMethod( "f" ) } }; |
@Test (dataProvider = "provider" ) |
public void withoutInjection( @NoInjection Method m) { |
Assert.assertEquals(m.getName(), "f" ); |
@Test (dataProvider = "provider" ) |
public void withInjection(Method m) { |
Assert.assertEquals(m.getName(), "withInjection" ); |
If you use Guice, TestNG gives you an easy way to inject your test objects with a Guice module:
@Guice (modules = GuiceExampleModule. class ) |
public class GuiceTest extends SimpleBaseTest { |
public void singletonShouldWork() { |
m_singleton.doSomething(); |
In this example,
GuiceExampleModule is expected to bind the interface
ISingleton to some concrete class:
public class GuiceExampleModule implements Module { |
public void configure(Binder binder) { |
binder.bind(ISingleton. class ).to(ExampleSingleton. class ).in(Singleton. class ); |
If you need more flexibility in specifying which modules should be used
to instantiate your test classes, you can specify a module factory:
@Guice (moduleFactory = ModuleFactory. class ) |
public class GuiceModuleFactoryTest { |
public void singletonShouldWork() { |
m_singleton.doSomething(); |
The module factory needs to implement the interface
IModuleFactory:
public interface IModuleFactory { |
* @param context The current test context |
* @param testClass The test class |
* @return The Guice module that should be used to get an instance of this |
Module createModule(ITestContext context, Class<?> testClass); |
Your factory will be passed an instance of the test context and the test class that TestNG needs to instantiate. Your
createModule
method should return a Guice Module that will know how to instantiate
this test class. You can use the test context to find out more
information about your environment, such as parameters specified in
testng.xml, etc...
1. What is dependency injection?
The general concept behind dependency injection is called
Inversion of Control. A class should not configure its dependencies statically but should
be
configured from outside.
Dependency injection is a concept which is not limited to
Java.
But we will look
at dependency injection from a Java point of view.
A Java class has a dependency on another class if it uses an
instance of this
class. For example a class which accesses a logger
service has a dependency on this service class.
Ideally Java classes should be as independent as possible
from
other
Java classes. This increases the possibility of reusing these
classes and to be able
to test them
independently from other classes.
If the Java class directly creates an instance of another class via
the
new
operator, it cannot be used (and tested) independently from
this class
and this is called a hard dependency. The following example shows a
class which has no hard dependencies.
package com.example.e4.rcp.todo.parts;
import java.util.logging.Logger;
import org.eclipse.e4.core.services.events.IEventBroker;
public class MyClass {
Logger logger;
public MyClass(Logger logger) {
this.logger = logger;
logger.info("This is a log message.")
}
}
Note
Please note that this class is just a normal Java class, there
is nothing special about it, except that it avoids direct object
creation.
Another class could analyze the dependencies of a class and create an
instance of
the
class, injecting objects into the defined dependency.
This can be
done
via the Java
reflection functionality by a framework
class usually called
the
dependency container.
This way the Java class has no hard dependencies, which means it does
not
rely on an instance of a certain class. This allows you to test
your class in isolation, for example by using
mock
objects.
Mock objects are objects, which act as if they are the real
object,
but only
simulate their behavior. Mock is an English word
which
means to
mimic
or imitate.
If dependency injection is used, a Java class can be tested in
isolation.
2. Using annotations to describe dependencies
In general different approaches exist to describe the
dependencies of a class. The most common approach is to use Java
annotations to describe the dependencies directly in the class.
The standard Java annotations for describing dependencies were
defined
in the Java
Specification Request 330 (JSR330). This specification
describes the
@Inject
and
@Named
annotations.
To decouple Java classes, its dependencies should be fulfilled
from the outside. A Java class would simply define its requirements
like in the following example:
public class MyPart {
@Inject private Logger logger;
@Inject private DatabaseAccessClass dao;
@Inject
public void createControls(Composite parent) {
logger.info("UI will start to build");
Label label = new Label(parent, SWT.NONE);
label.setText("Eclipse 4");
Text text = new Text(parent, SWT.NONE);
text.setText(dao.getNumber());
}
}
Note
Please note that this class uses the
new
operator for the user interface components. This implies that this
part of the code is nothing you plan to replace via your tests and
that you
made the decision to have a hard coupling to the
corresponding user interface toolkit.
3. Where can values be injected?
Dependency injection can be performed on:
-
the constructor of the class (construction injection)
-
a field (field injection)
-
the parameters of a method (method injection)
Dependency injection can be performed on static and on
non-static
fields
and
methods.
Tip
Avoid using dependency injection for statics as this typically leads
to confusion and has the following restrictions:
-
Static fields will be injected after the first object of the
class was created via DI, which means not to access the static
field in the constructor
-
Static fields should not be marked as final, otherwise the
compiler complains about them
-
Static methods are called only once after the first object of
the
class was created
4. Order in which injection is done
According to JSR330 the injection is done in the following order:
-
constructor injection
-
field injection
-
method injection
The order in which the methods or fields annotated with
@Inject
are called is not defined by JSR330. You cannot assume that the
methods or fields
are called in the order of declaration in the class.
Warning
As fields and methods parameters are injected after the constructor is
called, you cannot use injected member variables in the
constructor.
5. Java and dependency injection frameworks
You can use dependency injection without any additional framework
by providing classes with sufficient constructors or getter and
setter
methods.
A dependency injection framework simplifies the initialization
of the classes with the correct objects.
Two popular dependency injection frameworks are Spring and
Google Guice.
The usage of the Spring framework for dependency injection is
described in
Dependency Injection with the Spring Framework - Tutorial.
Great!!
ReplyDeleteIs there a example with Automation framework where dependency injection works?
ReplyDeleteGreat article Lot's of information to Read...Great Man Keep Posting and update to People..Thanks Ozempic Injections
ReplyDelete