E4 DI in unittests? [message #900647] |
Tue, 07 August 2012 23:20 |
|
Hi to anyone,
I want to write unittests regarding classes that are injected in my real app.
I want to inject these objects in my unittests too.
Is there a API for injecting objects and all their fields recursively?
What's the best way to test this enviroment?
Do you have an example to provide?
Thanks in advance
Best regards
Markus
|
|
|
Re: E4 DI in unittests? [message #900651 is a reply to message #900647] |
Wed, 08 August 2012 00:23 |
|
Not sure I understand your objective... Are you unit testing the DI framework or YourObject?
The only reason you would need the DI framework in your unit test is if you are unit testing the DI framework itself. If you are just testing YourObject (some object from your application), you should use some kind of mock framework [1] or similar. The DI framework will manage the re-injection when necessary [2]. Note that if two methods need injection with the same object, the order of injection is not API (AFAIK).
The one exception is if you are creating your own annotations for use with the DI framework. In testing @YourAnnotation you would need the DI framework (obviously) and you would mock the annotated object. If this is the case, I can help you setup the unit tests. (Note in this case you are unit testing the DI framework)
JD
[1] http://www.easymock.org/
[2] http://wiki.eclipse.org/Eclipse4/RCP/Dependency_Injection#Injection_Order
|
|
|
Re: E4 DI in unittests? [message #900680 is a reply to message #900651] |
Wed, 08 August 2012 07:21 |
|
Hi again,
my problem is the following:
I use DI to inject my own objects.
For example:
@Creatable
class A {
@Inject private B someField;
@Inject private C someOtherField;
}
@Creatable
class B {
@Inject C thirdField;
}
So from scratch, A.B.C is null, and I can't set it from outside.
OK, I could use mocking, but I cannot mock a private field with Mockito, I guess, so I have to make it at least package private.
Better would be to get these things injected, I think. This would simplify the test itself.
Could you give me a hint, what's best practice in this context?
Thanks a lot.
Best regards
Markus
|
|
|
|
Re: E4 DI in unittests? [message #900705 is a reply to message #900684] |
Wed, 08 August 2012 08:38 |
Eclipse User |
|
|
|
Regarding the recursiveness of the injection it happens automatically. When you inject calss A it will not get injected after all of it's fields are injected (excluding @Optional ones). If one of these fields is a class B which needs injection too, the B's injection (and of its fields ) will happen before A is ready to be injected.
If I got you correctly, you want to do DI on the JUnit class to harvest the @Creatable annotation and get the objects created by the injector so you don't mock them?
|
|
|
Re: E4 DI in unittests? [message #900788 is a reply to message #900680] |
Wed, 08 August 2012 13:35 |
|
To build off of your example here:
Markus Oley wrote on Wed, 08 August 2012 02:21
@Creatable
class A {
@Inject private B someField;
@Inject private C someOtherField;
}
@Creatable
class B {
@Inject C thirdField;
}
Just be very careful that there isn't a circular dependency such as:
@Creatable
class C {
@Inject /*qualifier*/ A fourthField;
}
From a java standpoint this certainly does not create any issues. The problem is if there aren't any usable instance of A / B / C in the context, injection will fail. (I don't know if there will be any listed error/warning)
If you need/want the fields to be private for some reason, you can use the same approach the DI engine uses with reflection for the mock objects. (See the code below or the attached file. But in general I agree with Tom, (whether using DI or not) if I need to test a field in a unit test, I make it package-private.
Also, the code below is only a sample and isn't quite 'ready to go'. A couple of things still need to be implemented, but it will at least get you started.
JD
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class MockInjector {
public Object getInstance(Class<?> testClass) {
Object instance = null;
try {
instance = testClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (instance == null) {
Constructor<?>[] constructs = testClass.getDeclaredConstructors();
// choose a constructor
Class<?>[] params = constructs[0].getParameterTypes();
Object[] paramInstances = new Object[params.length];
// need to create logic to provide parameters for desired constructor
try {
instance = constructs[0].newInstance(paramInstances);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return instance;
}
public void mockInject(Object testClassInstance, String fieldName, Object fieldValue) {
Class<?> clazz = testClassInstance.getClass();
Field testField = null;
try {
testField = clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
/*
* It doesn't matter if the field is private or public, by
* invoking setAccessible(true) it removes the accessibility
* checks performed by the JVM by setting a flag. It does not
* remove the security checks. Effectively this eliminates
* encapsulation.
*/
testField.setAccessible(true);
try {
testField.set(testClassInstance, fieldValue);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
|
|
|
|
|
|
Powered by
FUDForum. Page generated in 0.03985 seconds