I have a set of classes that are injected at runtime, because of a legacy code base. I want to write a unit test that checks the injector can satisfy all injected dependencies of those classes. I already have the list of classes to be injected available, and I can instantiate the injector, in Stage.TOOL, because otherwise it will do stuff like connect to a database.
My question is, how do I check those classes against the injector? I've tried injector.getMembersInjector(classToBeInjected), but this fails because injectors in Stage.TOOL do not support it. Basically, I don't need the injector instance, but I need it to check if it could be created.
For reference, here is my current implementation:
#AllArgsConstructor
#RunWith(Parameterized.class)
public class HtmlActionInjectTest {
#Parameters(name="{1}")
public static List<Object[]> parameters() {
return ImmutableList.of(classesUnderTest);
}
#BeforeClass
public static void setUp() {
injector = Guice.createInjector(Stage.TOOL, myLongListOfModules);
}
private static Injector injector;
private final Class<?> actionClass;
#Test
public void test() {
injector.getMembersInjector(actionClass);
}
}
I've also tried using the SPI api, using Elements.getElements(myModules), but I couldn't get it to tell me if the dependencies of the classToBeInjected can be satisfied.
Example of a class under test:
public class MyAction implements SomeInterface {
public MyAction(UnInjectableDependency dep) {
// can't be injected here for legacy reasons
}
#Inject void doInject(SomeDep dep) {
this.dep = dep;
}
#Override void someInterfaceMethod() { /* you get the idea */ }
}
I've found the solution with the help of a colleague.
Guice offers a class called InjectionPoint, which can be used to find all dependencies of the class to be injected. So, the solution looks like this:
#AllArgsConstructor
#RunWith(Parameterized.class)
public class HtmlActionInjectTest {
#Parameters(name="{1}")
public static List<Object[]> parameters() {
return ImmutableList.of(classesUnderTest);
}
#BeforeClass
public static void setUp() {
bindings = Guice.createInjector(Stage.TOOL, myLongListOfModules).getAllBindings().keySet();
}
private static Set<Key<?>> bindings;
private final Class<?> actionClass;
#Test
public void test() {
for (InjectionPoint point : InjectionPoint.forInstanceMethodsAndFields(actionClass)) {
for (Dependency<?> dependency : point.getDependencies()) {
assertTrue("injector cannot satisfy dependency " + dependency.getKey() + " in " + actionClass.getName(), bindings.contains(dependency.getKey()));
}
}
}
}
Related
I have a test utility for with I need to have a fresh instance per test method (to prevent that state leaks between tests). So far, I was using the scope "prototype", but now I want to be able to wire the utility into another test utility, and the wired instances shall be the same per test.
This appears to be a standard problem, so I was wondering if there is a "test method" scope or something similar?
This is the structure of the test class and test utilities:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTest {
#Autowired
private TestDriver driver;
#Autowired
private TestStateProvider state;
// ... state
// ... methods
}
#Component
#Scope("prototype") // not right because MyTest and TestStateProvider get separate instances
public class TestDriver {
// ...
}
#Component
public class TestStateProvider {
#Autowired
private TestDriver driver;
// ...
}
I'm aware that I could use #Scope("singleton") and #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) but this refreshes more than I need – a new TestDriver instance for each test would be enough. Also, this approach is error-prone because all tests using the TestDriver would need to know that they also need the #DirtiesContext annotation. So I'm looking for a better solution.
It is actually pretty easy to implement a testMethod scope:
public class TestMethodScope implements Scope {
public static final String NAME = "testMethod";
private Map<String, Object> scopedObjects = new HashMap<>();
private Map<String, Runnable> destructionCallbacks = new HashMap<>();
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!scopedObjects.containsKey(name)) {
scopedObjects.put(name, objectFactory.getObject());
}
return scopedObjects.get(name);
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
#Override
public Object remove(String name) {
throw new UnsupportedOperationException();
}
#Override
public String getConversationId() {
return null;
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
public static class TestExecutionListener implements org.springframework.test.context.TestExecutionListener {
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) testContext
.getApplicationContext();
TestMethodScope scope = (TestMethodScope) applicationContext.getBeanFactory().getRegisteredScope(NAME);
scope.destructionCallbacks.values().forEach(callback -> callback.run());
scope.destructionCallbacks.clear();
scope.scopedObjects.clear();
}
}
#Component
public static class ScopeRegistration implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope(NAME, new TestMethodScope());
}
}
}
Just register the test execution listener, and there will be one instance per test of all #Scope("testMethod") annotated types:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestExecutionListeners(listeners = TestMethodScope.TestExecutionListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
public class MyTest {
#Autowired
// ... types annotated with #Scope("testMethod")
}
I ran into the same problem some time ago and came to this solution:
Use Mocks
I wrote some methods to create specific mockito settings to add behavior to each mock.
So create a TestConfiguration class with following methods and bean definition.
private MockSettings createResetAfterMockSettings() {
return MockReset.withSettings(MockReset.AFTER);
}
private <T> T mockClass(Class<T> classToMock) {
return mock(classToMock, createResetAfterMockSettings());
}
and your bean definition will look like:
#Bean
public TestDriver testDriver() {
return mockClass(TestDriver .class);
}
MockReset.AFTER is used to reset the mock after the test method is run.
And finally add a TestExecutionListeners to your Test class:
#TestExecutionListeners({ResetMocksTestExecutionListener.class})
I have class A which is taking a set as guice dependency. The set is singleton. Below is the code example:
class A
{
private Set<InetAddress> set;
private String pingUriPath;
#Inject
public A(Set<InetAddress> set, #Named("pingUri") String pingUriPath)
{
this.set = set;
this.pingUriPath = pingUriPath; // this is used somewhere
}
public void storeValue(String str)
{
if(str.equals("abc"))
{
set.add(str);
}
}
}
Here is the guice module that injects dependency:
private class GuiceModule extends AbstractModule {
#Override
public void configure() {
bindConstant().annotatedWith(Names.named("pingUri")).to("/ping");
}
#Provides
#Singleton
Set<InetAddress> healthyTargets(){
return Sets.newConcurrentHashSet();
}
}
I want to mock the method storeValue and for that i have to mock the set. I am not able to mock the set using guice.
If i mock like below, it gives assertion error(no interactions with this mock)
#Mock
Set<InetAddress> mockHealthyTargets;
private class MockClassesModule extends AbstractModule {
#Override
public void configure() {
bindConstant().annotatedWith(Names.named("pingUri")).to("/ping");
}
#Provides
#Singleton
Set<InetAddress> healthyTargets(){
return Sets.newConcurrentHashSet();
}
}
public test_storeValue()
{
Injector injector = Guice.createInjector(new MockClassesModule());
A a = injector.getInstance(A.class);
a.storeValue("abc");
verify(mockHealthyTargets).add("abc")
}
If you have the need to use guice in your unit tests, something is most likely going the wrong direction. One of the biggest benefits of dependency injection is that testing becomes easy, because you can pass dependencies that are controlled by you.
I assume you want to test the class A and specifically the method storeValue. For this you don't even need mocking
#Test
public void test() {
// prepare dependencies
Set<InetAddress> set = Sets.newConcurrentHashSet();
String pingUri = "example.com";
// prepare input
String input = "someValue";
// prepare class under test
A classUnderTest = new A(set, pingUri);
// call class under test
classUnderTest.storeValue(input);
// check that what you expected happened
// in this case you expect that the dependency set now contains the input
assertThat(set, contains(input));
}
I have found what the mistake was, I should return mock when providing to my unit test. It should look like this:
#Mock
Set<InetAddress> mockHealthyTargets;
private class MockClassesModule extends AbstractModule {
#Override
public void configure() {
bindConstant().annotatedWith(Names.named("pingUri")).to("/ping");
}
#Provides
#Singleton
Set<InetAddress> healthyTargets(){
return mockHealthyTargets;
}
}
I want to replace an autowired class of a service in my spring boot app with a mocked implementation of that class that I created specifically for testing.
I chose to create this mocked implementation because the behaviour of this class is too complicated to mock using mockito as it requires multiple other mocks itself.
I am not able to work out how to inject this mocked implementation into the service.
Here is a minimal example of the situation:
#Service
public class ServiceIWantToTestImpl implements ServiceIWantToTest{
#Autowired
ComplicatedDependency complicatedDependency;
#Override
public void methodUsingDependency(){
String string = complicatedDependency.doSomething();
System.out.println(string);
}
}
public class MockComplicatedDependency implements ComplicatedDepencency{
public MockComplicatedDependency(...){
// Inject other mocked objects into this mock
}
public String doSomthing(){
// This would be a mocked version of this function for testing
return "test";
}
}
#RunWith(MockitoJUnitRunner.class)
public class TestingTheService(){
#InjectMock
private static ServiceIWantToTest serviceIWantToTest = new ServiceIWantToTestImpl();
#Mock
ComplicatedDependency mockComplicatedDependency;
#BeforeClass
public static void init(){
mockComplicatedDependency = new MockComplicatedDependency(...);
}
#Test
public void testAttempt(){
serviceIWantToTest.methodUsingDependency(); // This method calls complicatedDependency.doSomething() which does not run the mocked version in MockComplicatedDependency which I wanted to inject, and would always return null instead of the "test" string I put in this example.
}
}
Do you have to use Mockito annotations to setup dependencies for the class under test?
If that is not the main constraint why not just do the plain simple setup and introduce a constructor or a setter in ServiceIWantToTestImpl class for the ComplicatedDependency field and set the dependency in your test setup directly to whatever impl of ComplicatedDependency you like e.g.:
#Service
public class ServiceIWantToTestImpl implements ServiceIWantToTest {
#Autowired
ComplicatedDependency complicatedDependency;
public ServiceIWantToTestImpl() {
}
public ServiceIWantToTestImpl(ComplicatedDependency complicatedDependency) {
this.complicatedDependency = complicatedDependency;
}
#Override
public void methodUsingDependency(){
String string = complicatedDependency.doSomething();
System.out.println(string);
}
}
public class TestingTheService {
private static ServiceIWantToTestImpl serviceIWantToTest;
#BeforeClass
public static void init(){
serviceIWantToTest = new ServiceIWantToTestImpl(new MockComplicatedDependency());
}
#Test
public void testAttempt() {
serviceIWantToTest.methodUsingDependency();
}
}
That is one way.
To make it work with Mockito, You could to use #Spy instead of #Mock like this:
#RunWith(MockitoJUnitRunner.class)
public class TestingTheService {
#InjectMocks
private static ServiceIWantToTestImpl serviceIWantToTest = new ServiceIWantToTestImpl();
#Spy
private static ComplicatedDependency complicatedDependency = new MockComplicatedDependency();
#BeforeClass
public static void init() {
}
#Test
public void testAttempt() {
serviceIWantToTest.methodUsingDependency();
}
}
Though this is a bit of a hack. I strongly recommend that you read the JavaDoc of the #Spy annotation and make sure it's expected use is what you really need for your test.
I have a interface here
interface Idemo{
public int getDemo(int i);
}
And it's one implementation
class DemoImpl implements Idemo{
#Override
public int getDemo(int i){
return i+10;
}
}
And there is a class which has a dependency on Idemo
class Sample{
#Inject
Idemo demo;
public int getSample(int i){
return demo.getDemo(i);
}
}
Now say I want to test Sample class
public class SampleTest extends JerseyTest {
#Inject
Sample s;
#Override
protected Application configure() {
AbstractBinder binder = new AbstractBinder() {
#Override
protected void configure() {
bind(Demo.class).to(Idemo.class);
bind(Sample.class).to(Sample.class); //**doesn't work**
}
};
ResourceConfig config = new ResourceConfig(Sample.class);
config.register(binder);
return config;
}
#Test
public void test_getSample() {
assertEquals(15, s.getSample(5)); //null pointer exception
}
}
Here the Sample instance is not getting created and s remains null.I suppose this is because by the time the execution reaches line where binding is specified this test class has already been created.But I am not sure.With Spring Autowired instead of jersey CDI the same works
Had Sample been a resource/controller class the test framework would create an instance of it with no need to inject it but is it possible to test any other non-web class using Jersey DI ?
The reason it works with Spring is that the test class is managed by the Spring container by using #RunWith(SpringJUnit4ClassRunner.class). The runner will inject all managed objects into the test object. JerseyTest is not managed this way.
If you want, you can create your own runner, but you need to understand a bit how HK2 (Jersey's DI framework) works. Take a look at the documentation. Everything revolves around the ServiceLocator. In a standalone, you might see something like this to bootstrap the DI container
ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
ServiceLocator locator = factory.create(null);
ServiceLocatorUtilities.bind(locator, new MyBinder());
Then to get the service, do
Service service = locator.getService(Service.class);
In the case of the test class, we don't need to gain any access to the service object, we can simply inject the test object, using the ServiceLocator:
locator.inject(test);
Above, test is the test class instance that gets passed to us in our custom runner. Here is the example implementation of a custom runner
import java.lang.annotation.*;
import org.glassfish.hk2.api.*;
import org.glassfish.hk2.utilities.*;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.*;
public class Hk2ClassRunner extends BlockJUnit4ClassRunner {
private final ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
private Class<? extends Binder>[] binderClasses;
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public static #interface Binders {
public Class<? extends Binder>[] value();
}
public Hk2ClassRunner(Class<?> cls) throws InitializationError {
super(cls);
Binders bindersAnno = cls.getClass().getAnnotation(Binders.class);
if (bindersAnno == null) {
binderClasses = new Class[0];
}
}
#Override
public Statement methodInvoker(FrameworkMethod method, final Object test) {
final Statement statement = super.methodInvoker(method, test);
return new Statement() {
#Override
public void evaluate() throws Throwable {
ServiceLocator locator = factory.create(null);
for (Class<? extends Binder> c : binderClasses) {
try {
ServiceLocatorUtilities.bind(locator, c.newInstance());
} catch (InstantiationException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
locator.inject(test);
statement.evaluate();
locator.shutdown();
}
};
}
}
In the runner, the methodInvoker is called for every test method, so we are creating a fresh new set of objects for each test method called.
Here is a complete test case
#Binders({ServiceBinder.class})
#RunWith(Hk2ClassRunner.class)
public class InjectTest {
public static class Service {
#Inject
private Demo demo;
public void doSomething() {
System.out.println("Inside Service.doSomething()");
demo.doSomething();
}
}
public static class Demo {
public void doSomething() {
System.out.println("Inside Demo.doSomething()");
}
}
public static class ServiceBinder extends AbstractBinder {
#Override
protected void configure() {
bind(Demo.class).to(Demo.class);
bind(Service.class).to(Service.class);
}
}
#Inject
private Service service;
#Test
public void testInjections() {
Assert.assertNotNull(service);
service.doSomething();
}
}
I was facing the same situation but in the context of running some integrations test that needs to have some of the singletons that my application have already defined.
The trick that I found is the following. You just need to create a normal test class or a standalone that use the DropwizardAppRule
In my case, I use JUnit as I was writing some integration test.
public class MyIntegrationTest{
//CONFIG_PATH is just a string that reference to your yaml.file
#ClassRule
public static final DropwizardAppRule<XXXConfiguration> APP_RULE =
new DropwizardAppRule<>(XXXApplication.class, CONFIG_PATH);
}
The #ClassRule will start your application like is said here . That
means you will have access to everything and every object your application needs to start. In my case, I need to get access to a singleton for my service I do that using the #Inject annotation and the #Named
public class MyIntegrationTest {
#ClassRule
public static final DropwizardAppRule<XXXConfiguration> APP_RULE =
new DropwizardAppRule<>(XXXAplication.class, CONFIG_PATH);
#Inject
#Named("myService")
private ServiceImpl myService;
}
Running this will set to null the service as #Inject is not working because we don't have at this point anything that put the beans into the references. There is where this method comes handy.
#Before
public void setup() {
ServiceLocator serviceLocator =((ServletContainer)APP_RULE.getEnvironment().getJerseyServletContainer()).getApplicationHandler().getServiceLocator();
//This line will take the beans from the locator and inject them in their
//reference, so each #Inject reference will be populated.
serviceLocator.inject(this);
}
That will avoid creating other binders and configurations outside of the existing on your application.
Reference to the ServiceLocator that DropwizardAppRule creates can be found here
We use a custom Guice scope, #TestScoped, for some of our JUnit tests that lasts for a single test method, and a JUnit #Rule to enter and exit the scope appropriately. It looks like this:
public class MyJUnitTest {
#Rule public CustomRule customRule = new CustomRule(MyModule.class);
#Inject private Thing thing;
#Test
public void test1() {
// Use "thing"
}
#Test
public void test2() {
// Assuming "Thing" is #TestScoped, we'll have a new instance
}
}
We're starting to use TestNG for some of our tests in other projects, and we'd like to have a similar pattern. So far we've come up with this:
#Listeners(CustomTestNGListener.class)
#Guice(modules = MyModule.class)
public class MyTestNGTest {
#Inject private Provider<Thing> thingProvider;
#Test
public void test1() {
Thing thing = thingProvider.get();
// Use "thing"
}
#Test
public void test2() {
Thing thing = thingProvider.get();
// Assuming "Thing" is #TestScoped, we'll have a new instance
}
}
public class CustomTestNGListener implements IHookable {
#Override
public void run(IHookCallBack callBack, ITestResult testResult) {
TestScope.INSTANCE.enter();
try {
callBack.runTestMethod(testResult);
} finally {
TestScope.INSTANCE.exit();
}
}
}
There are a couple issues with this design:
Unlike JUnit, TestNG uses the same instance of the test class for each method. That means we have to inject Provider<Thing> instead of just Thing, which is awkward.
For some reason, CustomTestNGListener is running on all of our tests, even ones that don't have that #Listeners(CustomTestNGListener.class) annotation. I've worked around this by explicitly checking for that annotation in the listener itself, but it feels like a hack (though I do see that MockitoTestNGListener does the same thing).
Does someone with more familiarity with TestNG have any suggestions for dealing with these issues?
Instead of
public class MyTestNGTest {
#Inject private Provider<Thing> thingProvider;
#Test
public void test1() {
Thing thing = thingProvider.get();
In TestNG you can used
public class MyTestNGTest {
#Inject
private Thing thingInjected;
private Thing thing;
#BeforeTest
public void doBeforeTest() {
thing = thingInjected.clone();
}
Or just call thingProvider.get() in doBeforeTest(), it's better in you have a lot of # Test
public class MyTestNGTest {
#Inject private Provider<Thing> thingProvider;
private Thing thing;
#BeforeTest
public void doBeforeTest() {
thing = thingProvider.get();
}