#Inject field during test phase - java

I'm adding logging functionality to my application and I'm using slf4j implemented by log4j. To simplify my code I have a helper class to produce a logger (the idea is not mine, I took it from Beginning Java EE 7 by Antonio Goncalves), the class is like this:
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggerProducer {
#Produces
public Logger createLogger(InjectionPoint injPoint) {
return LoggerFactory.getLogger(injPoint.getMember().getDeclaringClass());
}
}
So in my classes all I have to do (in theory) is:
public class SomeClass {
#Inject private Logger logger; // this could be private or default, doesn't matter
}
The problem is that during test phase I don't have CDI enabled because I'm running the code outside the container. I can't inject manually because I have some abstract classes with their own logger, so I can't instantiate that class and assign it a Logger instance.
Is there any way to "enable" CDI during test phase? My project is built with Maven and I'll be deploying to a Wildfly 10 Server. Thanks in advance for your answers.
Note
I cannot do something like
public class SomeClassTest {
private SomeClass someClass; // this is the class I want to test
#Before
public void init() {
someClass.logger = LoggerFactory.getLogger(SomeClass.class);
}
#Test
public void someTest(){...}
}
because I have some abstract classes with their own private Logger logger property and I need to keep it that way (I can't declare it protected ) because I want to keep a trace of where a message is thrown, so I need to keep the logger private. E.g. I have something like
public abstract class MyAbstract {
#Inject
private Logger logger;
}
public class MyConcrete extends MyAbstract {
#Inject
private Logger logger;
}
I could, from the test class, set MyConcrete.logger as default but I cant do that forMyAbstract.loggerbecause they are in different packages and I can't instantiateMyAbstract`, am I explaining correctly?
Note 2
My project structure is something like this:
package common
import org.slf4j.logger
...
public abstract class Generic {
#Inject
private Logger logger;
public void doSomethingGeneric(){
// do something and log it
}
}
package specific
import ...
public class Concrete extends Generic{
#Inject
Logger logger;
// some concrete methods...
}
package specific
public class ConcreteTest {
private Concrete concrete;
#Before
public void init() {
concrete = new Concrete();
concrete.logger = LoggerFactory.getLogger(Concrete.class); // Dependency injection by hand
}
// some #Test methods
}
Eventually, a #Test method from ConcreteTest calls Concrete.doSomethingGeneric() as it's inherited to the concrete instance. The problem is that doSomethingGeneric() logs with the Generic logger and I don't know how to satisfy that dependency in a clean way (I prefer not to hack my code with setter methods unnecessary for production)

You can do this, using mockito test units:
#RunWith(MockitoJUnitRunner.class)
public class MyBeanTest {
#Spy
private Logger logger = LoggerProducer.getLogger();
#InjectMocks
private MyBean myTestBean; // = new MyBean(parameters). explicit if no default constructir
#Test
public void verifySomeLogic() {
myTestBean.doSomething();
}
}
Here, the mockito runtime and proxy will manage the injection, and you can even do verification on the logger itself.

Related

Injecting Spring Boot component with Mockito mocks

Here is my GitHub repo for reproducing the exact issue.
Not sure if this is a Spring Boot question or a Mockito question.
I have the following Spring Boot #Component class:
#Component
class StartupListener implements ApplicationListener<ContextRefreshedEvent>, KernelConstants {
#Autowired
private Fizz fizz;
#Autowired
private Buzz buzz;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// Do stuff involving 'fizz' and 'buzz'
}
}
So StartupListener has no constructor and is intentionally a Spring #Component that gets its properties injected via #Autowired.
The #Configuration class providing these dependencies is here, for good measure:
#Configuration
public class MyAppConfiguration {
#Bean
public Fizz fizz() {
return new Fizz("OF COURSE");
}
#Bean
public Buzz buzz() {
return new Buzz(1, true, Foo.Bar);
}
}
I am now trying to write a JUnit unit test for StartupListener, and I have been using Mockito with great success. I would like to create a mock Fizz and Buzz instance and inject StartupListener with them, but I'm not sure how:
public class StartupListenerTest {
private StartupListener startupListener;
#Mock
private Fizz fizz;
#Mock
price Buzz buzz;
#Test
public void on_startup_should_do_something() {
Mockito.when(fizz.calculateSomething()).thenReturn(43);
// Doesn't matter what I'm testing here, the point is I'd like 'fizz' and 'buzz' to be mockable mocks
// WITHOUT having to add setter methods to StartupListener and calling them from inside test code!
}
}
Any ideas as to how I can accomplish this?
Update
Please see my GitHub repo for reproducing this exact issue.
You can use #SpyBeaninstead of #MockBean, SpyBean wraps the real bean but allows you to verify method invocation and mock individual methods without affecting any other method of the real bean.
#SpyBean
private Fizz fizz;
#SpyBean
price Buzz buzz;
You can use #MockBean to mock beans in ApplicationContext
We can use the #MockBean to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context.
If no bean of the same type is defined, a new one will be added. This annotation is useful in integration tests where a particular bean – for example, an external service – needs to be mocked.
To use this annotation, we have to use SpringRunner to run the test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class MockBeanAnnotationIntegrationTest {
#MockBean
private Fizz fizz;
}
And i will also suggest to use #SpringBootTest
The #SpringBootTest annotation tells Spring Boot to go and look for a main configuration class (one with #SpringBootApplication for instance), and use that to start a Spring application context.
you can do something likewise,
#RunWith(MockitoJUnitRunner.class)
public class StartupListenerTest {
#Mock
private Fizz fizz;
#Mock
price Buzz buzz;
#InjectMocks
private StartupListener startupListener;
#Test
public void on_startup_should_do_something() {
Mockito.when(fizz.calculateSomething()).thenReturn(43);
....
}
}
Here is a simple example that just uses plain Spring.
package com.stackoverflow.q54318731;
import static org.junit.Assert.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
#SuppressWarnings("javadoc")
public class Answer {
/** The Constant SPRING_CLASS_RULE. */
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
/** The spring method rule. */
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
static final AtomicInteger FIZZ_RESULT_HOLDER = new AtomicInteger(0);
static final int FIZZ_RESULT = 43;
static final AtomicInteger BUZZ_RESULT_HOLDER = new AtomicInteger(0);;
static final int BUZZ_RESULT = 42;
#Autowired
ConfigurableApplicationContext configurableApplicationContext;
#Test
public void test() throws InterruptedException {
this.configurableApplicationContext
.publishEvent(new ContextRefreshedEvent(this.configurableApplicationContext));
// wait for it
TimeUnit.MILLISECONDS.sleep(1);
assertEquals(FIZZ_RESULT, FIZZ_RESULT_HOLDER.get());
assertEquals(BUZZ_RESULT, BUZZ_RESULT_HOLDER.get());
}
#Configuration
#ComponentScan //so we can pick up the StartupListener
static class Config {
final Fizz fizz = Mockito.mock(Fizz.class);
final Buzz buzz = Mockito.mock(Buzz.class);
#Bean
Fizz fizz() {
Mockito.when(this.fizz.calculateSomething())
.thenReturn(FIZZ_RESULT);
return this.fizz;
}
#Bean
Buzz buzz() {
Mockito.when(this.buzz.calculateSomethingElse())
.thenReturn(BUZZ_RESULT);
return this.buzz;
}
}
#Component
static class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private Fizz fizz;
#Autowired
private Buzz buzz;
#Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
FIZZ_RESULT_HOLDER.set(this.fizz.calculateSomething());
BUZZ_RESULT_HOLDER.set(this.buzz.calculateSomethingElse());
}
}
static class Fizz {
int calculateSomething() {
return 0;
}
}
static class Buzz {
int calculateSomethingElse() {
return 0;
}
}
}
If you modify your StartupListenerTest to just focus on the StartupListener class
i.e. add the class to the SpringBootTest annotation
#SpringBootTest(classes= {StartupListener.class})
You will get a different error, but it's more focused on the class you're trying to test.
onApplicationEvent method will fire before the test runs. This means you won't have initialized your mock with when(troubleshootingConfig.getMachine()).thenReturn(machine); and so there's no Machine returned when getMachine() is called, hence the NPE.
The best approach to fix this really depends on what you're trying to achieve from the test. I would use an application-test.properties file to set up the TroubleShootingConfig rather than use an #MockBean. If all you're doing in your onApplicationEvent is logging then you could use #SpyBean as suggested in another answer to this question. Here's how you could do it.
Add an application-test.properties to resources folder so it's on the classpath:
troubleshooting.maxChildRestarts=4
troubleshooting.machine.id=machine-id
troubleshooting.machine.key=machine-key
Add #Configuration to TroubleshootingConfig
#Configuration
#ConfigurationProperties(prefix = "troubleshooting")
public class TroubleshootingConfig {
private Machine machine;
private Integer maxChildRestarts;
... rest of the class
Change StartupListenerTest to focus on the classes your testing and spy on the TroubleshootingConfig. You also need to #EnableConfigurationProperties
#RunWith(SpringRunner.class)
#SpringBootTest(classes= {TroubleshootingConfig.class, StartupListener.class})
#EnableConfigurationProperties
public class StartupListenerTest {
#Autowired
private StartupListener startupListener;
#SpyBean
private TroubleshootingConfig troubleshootingConfig;
#MockBean
private Fizzbuzz fizzbuzz;
#Mock
private TroubleshootingConfig.Machine machine;
#Mock
private ContextRefreshedEvent event;
#Test
public void should_do_something() {
when(troubleshootingConfig.getMachine()).thenReturn(machine);
when(fizzbuzz.getFoobarId()).thenReturn(2L);
when(machine.getKey()).thenReturn("FLIM FLAM!");
// when
startupListener.onApplicationEvent(event);
// then
verify(machine).getKey();
}
}

How to Mock an injected object that is not declared in Module?

For a dagger2 module
#Module
public class MyModule {
#Provides #Singleton public RestService provideRestService() {
return new RestService();
}
#Provides #Singleton public MyPrinter provideMyPrinter() {
return new MyPrinter();
}
}
We could have the test module as Test
public class TestModule extends MyModule {
#Override public MyPrinter provideMyPrinter() {
return Mockito.mock(MyPrinter.class);
}
#Override public RestService provideRestService() {
return Mockito.mock(RestService.class);
}
}
However if for a class as below that is not declared in the dagger module...
public class MainService {
#Inject MyPrinter myPrinter;
#Inject public MainService(RestService restService) {
this.restService = restService;
}
}
How do I create a mock of MainService as above.
Note, I'm not planning to perform test for MainService as per share in https://medium.com/#fabioCollini/android-testing-using-dagger-2-mockito-and-a-custom-junit-rule-c8487ed01b56#.9aky15kke, but instead, my MainService is used in another normal class that I wanted to test. e.g.
public class MyClassDoingSomething() {
#Inject MainService mainService;
public MyClassDoingSomething() {
//...
}
// ...
public void myPublicFunction() {
// This function uses mainService
}
}
This is definitely not answering your question, but in my honest opinion it is related, it's helpful and too big for a comment.
I'm often facing this question and I end always doing "Constructor dependency injection". What this means is that I no longer do field injection by annotating the field with #Inject but pass the dependencies in the constructor like so:
public class MyClassDoingSomething implements DoSomethig {
private final Service mainService;
#Inject
public MyClassDoingSomething(Service mainService) {
this.mainService = mainService;
}
}
Notice how the constructor now receives the parameter and sets the field to it and is also annotated with #Inject? I also like to make these classes implement an interface (also for MyService) - Amongst several other benefits I find it makes the dagger module easier to write:
#Module
public class DoSomethingModule {
#Provides #Singleton public RestService provideRestService() {
return new RestService();
}
#Provides #Singleton public MyPrinter provideMyPrinter() {
return new MyPrinter();
}
#Provides #Singleton public Service provideMyPrinter(MyService service) {
return service;
}
#Provides #Singleton public DoSomethig provideMyPrinter(MyClassDoingSomething something) {
return something;
}
}
(This assumes that MyService implements or extends Service)
By now it seems you already know that dagger is able to figure out the dependency graph by itself and build all the objects for you. So what about unit testing the class MyClassDoingSomething? I don't even use dagger here. I simply provide the dependencies manually:
public class MyClassDoingSomethingTest {
#Mock
Service service;
private MyClassDoingSomething something;
#Before
public void setUp() throws Exception {
MockitoAnnotations.init(this);
something = new MyClassDoingSomething(service);
}
// ...
}
As you see, the dependency is passed through the constructor manually.
Obviously this doesn't work if you're coding something that doesn't have a constructor that can be invoked by you. Classical examples are android activities, fragments or views. There are ways to achieve that, but personally I still think you can somehow overcome this without dagger. If you are unit testing a view that has a field #Inject MyPresenter myPresenter, usually this field will have package access that works fine in the tests:
public class MyViewTest {
#Mock MyPresenter presenter;
private MyView view;
#Before
public void setUp() throws Exception {
MockitoAnnotations.init(this);
view.myPresenter = presenter;
}
}
Note that this only works if both MyViewTest and MyView are in the same package (which often is the case in android projects).
At the end of the day if you still want to use dagger for the tests, you can always create "test" modules and components that can inject by declaring methods in the component like:
#Inject
public interface MyTestComponent {
void inject(MyClassDoingSomething something);
}
I find this approach ok-ish, but throughout my development years I prefer the first approach. This also has reported issues with Robolectric that some setup in the build.gradle file is required to actually make the dagger-compiler run for the tests so the classes are actually generated.

Create instance of call with autowire

I have to create a instance of a class, that have autowired elements, for test.
public class MyClass extends SomeOtherClass {
#Autowired
public MyClass(OtherClass1 one, OtherClass2 two){
super(one, two)
}
}
How can i in code create instance of this class, with the arguments wired in though spring?
Your test can be made Spring-aware if you use the SpringJUnit4ClassRunner to read in your Spring Context to be used in the test. For instance:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"the-config.xml"})
public final class MyClassTests {
#Autowired
private MyClass testee;
#Test
public void testSomething() {
assertThat(testee).doesSomethingExpected();
}
}
Note that you should reuse as much of your production config as possible and not create a parallel Spring Context config that mirrors it.
Instead of passing the other elements in as constructor arguments, you Autowire them as properties. Spring will then inject the objects.
public class MyClass extends SomeOtherClass {
#Autowired
private OtherClass1 one;
#Autowired
private OtherClass2 two
public MyClass(){
super(one, two)
}
}
Edit: Based on http://www.mkyong.com/spring/spring-auto-wiring-beans-with-autowired-annotation/, adding #Autowired to the constructor is also valid.
If you want to Autowire MyClass, you must annotate it with #Component or a similar annotation such as #Service.
#Component
public class MyClass extends SomeOtherClass
Then, you can use it in other classes
public class ClassThatUsesMyClass {
#Autowire
private MyClass myClass;
}

Groovy / Spock test a Java class that has a member #Inject

I am trying to test a Java class that has a member injected using #Inject.
I want to test the someMethod
public class SomeJavaClass {
#Inject
private SomeFactory someFactory;
private SomeDAO someMethod(Map someMap) {
SomeDAO someDAO = someFactory.create();
//some code
}
}
My class does not have a explicit constructor
If your field is private, and you don't use constructors, how do you inject it? I will assume that this was a mistake, because if you inject something from the outside, you need to provide an interface for it.
So here's a spock spec that does what you ask, but exposes someFactory as a protected member:
import spock.lang.Specification
import spock.lang.Subject
import javax.inject.Inject
interface SomeFactory {
SomeDAO create()
}
class SomeDAO {
}
class SomeJavaClass {
#Inject
protected SomeFactory someFactory;
protected SomeDAO someMethod(Map someMap) {
SomeDAO someDAO = someFactory.create();
//some code
}
}
class SomeJavaClassSpec extends Specification {
def someFactory = Mock(SomeFactory)
#Subject
SomeJavaClass subject = new SomeJavaClass(
someFactory: someFactory
)
def "It uses a factory to create some object"() {
when:
subject.someMethod([:])
then:
1 * someFactory.create() >> new SomeDAO()
}
}
You can also use the spock collaborators extension annotations to inject your collaborator Mocks automatically:
https://github.com/marcingrzejszczak/spock-subjects-collaborators-extension
Of course you can inject to private fields (Guice and Spring can do that). You can use my extension but it would be much better if you added the constructor since then it follows the OOP practices.

Injecting mock into class inherited by class under test?

I'm writing a unit test for a class A which extends class B. I'm using Mockito and I want to mock a org.slf4j.logger both classes to use. The problem is when class A calls on a method from class B, the mocked logger is not injected into class B so I get a NPE. Is there a way to successfully test this?
public class ClassA extends ClassB {
#Inject
private static final Logger LOGGER = LoggerFactory.getLogger(ClassA.class);
public void classAMethod {
LOGGER.debug("u wot m8");
this.classBMethod();
}
public class ClassB {
#Inject
private static final Logger LOGGER = LoggerFactory.getLogger(ClassB.class);
public void classBMethod {
LOGGER.debug("u wot m8");
}
}
public class ClassATest {
#InjectMocks
private ClassA classA = new ClassA
#Mock
private Logger mockLogger;
#Test
public void testClassA() {
classA.classAMethod ();
verify(mockLogger, (times, 2)).debug(Mockito.anyString());
}
}
Mockito can't inject static final fields. In fact, nobody can, since by definition, a final field can't be assigned twice. And Mockito doesn't inject static fields anyway. Only instance fields.
See the documentation:
However fields that are static or final will be ignored.
If you are using Java EE 6 #Inject it will not work outside the CDI container, this is part of the spec. If this is a unit test you have to provide it by yourself.

Categories

Resources