Spring Autowire bean NullPointerException during Groovy tests but works fine during Runtime - spring-boot

I am using the prometheus library for getting metrics of my Spring Boot application (REST API). I am using the library io.prometheus.simpleclient:0.4.0 and I am including it in my Maven pom.xml. I am using the Counter and #Autowiring (I've tried both field and constructor injection) it to one of my own classes, like such
MyCustomMetricsClass.java
#Component
public MyCustomMetricsClass {
#Autowire
private Counter counterBean;
public void myOwnMetricsMethod() {
counterBean.inc();
// do some stuff
}
THEN, I am #Autowiring this MyCustomMetricsClass into my Service class, MyServiceClass.java, where it seems to run fine when I run my API locally using Spring Boot embedded tomcat on port 8080 (localhost:8080). I can hit endpoints and the metrics are being reported correctly at the actuator endpoint (localhost:8080/actuator/metrics). e.g.
MyServiceClass.java
public MyServiceClass {
#Autowire
private MyCustomMetricsclass myMetrics;
public void genericServiceMethod() {
myMetrics.MyOwnMetricsMethod(); // NULL POINTER EXCEPTION ONLY DURING TEST SCOPE (GROOVY)
}
The problem is, when I run mvn install, which triggers the local Groovy unit tests I have written, I keep getting a NULL POINTER EXCEPTION. With the debugger, I can debug the Groovy unit tests and see in my Service class, the myMetrics variable is NULL. But I don't understand why it works fine at runtime, also, I have annotated the MyCustomMetricsClass as a #Component annotation, so it should be a bean being scanned by Spring Component scan.
This is a multi-module project ; with the structure below
my-project (root, contains root pom.xml)
- my-api (module, contains RestController. has its own pom.xml)
- my-service (module. contains service classes, has its own pom.xml)
- my-model (module, contains all POJO/DTO model classes, has its own pom.xml)
Am I missing some dependency on my classpath? Why does it work at runtime but not during tests? (all my dependencies should have default scope) Is the autowiring broken?

Can you share the code from your unit test?
At a guess, you're using a mocking framework, maybe Mockito?
If this assumption is true, remember, your unit test won't be running up the full Spring context, so no auto-wiring will take place. You will need to inject mocks for the autowired components.
e.g.:
#RunWith(MockitoJUnitRunner.class)
public class MyServiceClassTest {
#Mock
private MyCustomMetricsClass myCustomMetricsClass;
#InjectMocks
private MyServiceClass myServiceClass;
#Test
public void shouldDoTesting() {
myServiceClass. genericServiceMethod();
verify(myMetrics).MyOwnMetricsMethod();
}
}

Related

Are the project beans already instantiated when we try to run a junit test in spring boot

I am new to Spring and spring boot.
For my spring boot application which is a rest controller, I have some beans along with my data source.
I use my data source to create jdbc template. Now when I am in my rest controller code, I have all these beans #Autowired and they work perfectly fine.
My query is regarding the junit testing part.
When I write my test code inside src/test/java and when I execute my test class within IDE, are the beans defined in my src/main/javacode, instantiated before test case execution?
You might use the same container, or instantiate another container particularly for testing purposes, for which you'll provide a configuration of that other Spring Container separately:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:test-context.xml")
public class SomeClassTest{...}
However, you can also enable support for loading your Application Context and then use the #Autowired fields in your JUnit fixtures, which also works fine too:
#RunWith(SpringRunner.class)
public class SomeTestClass {
....
#Autowired
ApplicationContext context;
....
}
From here, you can get any bean you wish.

MockBean and MyBatis Mapper not working together (as they did before Spring Boot 2.2.7)

I am using MyBatis Spring Boot Starter version 2.1.3. Ever since moving to Spring Boot 2.2.7 (I've also tried 2.2.8 and 2.3.1 with the same results), I've had a problem using the MockBean annotation to mock a MyBatis interface (e.g. DAO). Let's say I have an interface like this one:
#Mapper
#Repository
public interface OrderDAO {
int insertOrder(#Param("order") Order order);
}
I'd like to execute an integration test and mock this mapper within my OrderService that contains a field of type OrderDAO.
My integration test is annotated with #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) and contains this field:
#MockBean
private OrderDAO orderDAO;
When I run a test such as this and look at what the references to the OrderDAO objects are, in my integration test I see this:
com.example.dao.OrderDAO$MockitoMock$1819884459
But inside my OrderService class the field is this:
com.sun.proxy.$Proxy179 (org.apache.ibatis.binding.MapperProxy#37d9310e)
So, calls to Mockito.verify obviously don't work because my mock has not been injected into my OrderService class. Now, very oddly, I found that adding this code makes everything work:
#TestConfiguration
static class MockConfig {
#Bean
public OrderDAO orderDAO() {
return Mockito.mock(OrderDAO.class);
}
}
Adding this nested class along with adding the ContextConfiguration annotation on the integration test class, and now the object that gets injected into the OrderService class is the MockitoMock -- the same object that is referenced by the MockBean annotated field in the test class. I didn't have to do this with Spring Boot 1.2.6 and earlier and I couldn't find any reference to a change that would have caused this (although perhaps I didn't search long enough).
So, I am wondering if I am doing something incorrectly, or, am I missing something I should be doing? It seems like this should just work like it did before, which the need for this extra nested TestConfiguration class. Appreciate any insights anyone can provide. Thanks.
As mentioned by ave in the comments, I had to add the name to the mock bean annotation to get it to work
#MockBean(name = "orderDAO")
private OrderDAO orderDAO;

Spock with Spring Boot and Camel: Zero interactions with detached mock

I am having some issues with testing my camel context with spring boot.
I am using spring boot 1.5.6, spock 1.1-groovy-2.4, camel 2.19.2, and camel-spring-boot-starter 2.19.2.
I am using a spock mock, and I'm using the DetachedMockFactory in a #TestConfiguration class. All of my beans use constructor injection. I am injecting a mocked #Repository into one of the processor #Components, and I am also injecting it into my test class to define interactions.
I have my test annotated with #SpringBootTest with the classes list including all Processor implementations, and all RouteBuilder extensions. I also have an '#Import' with my TestConfiguration class. I am even using constructor injection for this repository bean in my test!
But it seems that the mock that is injected into the test class is not the one that is in use. Does anyone have an idea what could be wrong? I have tried #DirtiesContext to reload the context both before and after each test, but that did not help.
Problems with DetachedMocks not behaving correctly, e.g., appearing to be the same instance, are usually caused by some framework wrapping them in proxies. For example this can be caused by #Transactional annotation in Spring, which creates a proxy to facilitate jdbc-session management. See also issue #758
For spring you can use the methods of AopUtils (jdoc). The simple way is to use AopUtils.isAopProxy to check if it is proxied by spring an then unwrap it.
public static <T> T getTargetObject(Object proxy) throws Exception {
if (AopUtils.isAopProxy(proxy)) {
return (T) ((Advised) proxy).getTargetSource().getTarget();
} else {
return (T) proxy;
}
}
And in a Test
def "sample service test"() {
given:
def sampleRepositryMock = getTargetObject(sampleRepositry)
when:
sampleService.doSomething() // simply invoke sampleRepositry.doSomething() in it
then:
1 * sampleRepositryMock.doSomething()
0 * _
}
Edit: Since Spock 1.2 there is an extension to automatically unwrap injected beans #UnwrapAopProxy.
#Inject
#UnwrapAopProxy
SampleRepositry sampleRepositryMock
If someone comes up with the same problem.
Spock added additional #UnwrapAopProxy that will do the job for you instead of the util method mentioned above. You can also drop the DetachedMockFactory
#SpringSpy
#UnwrapAopProxy
Service service

WebApplicationContext is always null in a spring boot test

My test class looks like this
#SpringBootTest(webEnvironment=WebEnvironment.MOCK)
public class sampleClassTest{
#Autowired
private WebApplicationContext wac;
}
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
In the setup method, wac is always null. From spring boot documentation, #SpringBootTest(webEnvironment=WebEnvironment.MOCK) always created a mock WebapplicaitonContext.
So I would expect it get autowired in the code above which doesn't happen.
Can someone tell me how to go about creating a webapplicationContext in this case so that it's not null like in my case ?
UPDATE
I am running spring boot tests invoking them from a class with springboot annotation.
Both test (springboottest) and calling class (springboot) application are in the same spring boot project under src/main/java.
I have nothing under src/main/test. I have done in this way because if classes from src/main/java want to call a test class then, it isn't really a test class.
Now, the problem is that I can't use runWith(SpringRunner.class) in springbootTest class. If I did that to get a mock webApplicationContext then, it gives me this error:
javax.management.InstanceAlreadyExistsException: org.springframework.boot:type=Admin,name=SpringApplication
I am not sure how to do about this.
To use #SpringBootTest you need to use Spring Framework's test runner. Annotate your test class with #RunWith(SpringRunner.class).
If someone is struggling with this issue in 2022 - please keep my defined precondions in mind. If you are using #SpringBootTest with defined port and constructor auto-wiring of the test class, the application context might be null.
It seems that the constructor dependency injection is eager and the cache aware context delegate of Spring is searching for a web application context which is no available yet. If you use field auto-wiring your test might run in a deterministic manner.
Whoever is facing this issue, make sure your spring boot starter parent version is compatible with spring cloud version in pom.xml
I was facing same issue, i resolved it by doing same.

Spring tries to autowire Mockito mocks

I have a spring managed application in which I like for my service layer to be mocked. So I created a Spring Application Java Config and returned a mock of the actual service.
For e.g,
#Bean
#Profile("resource")
public MyService mockService() {
return mock(MyService.class)
}
And then the MyService goes as
class MyService {
#Autowired
private MyDao dao;
}
When Spring creates bean of name "mockService", it also tries to Autowire MyDao on the mock ? This in my opinion defeats the purpose of mocking. Is this the expected behaviour, whats the workaround ?
So bottomline, its best practice to code to interfaces rather than concrete classes particularly if you are wanting to write focused tests on specific layers.
Well, I suppose you're trying to execute an integration test, but using some mocks, doing something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {Application.class, YourMockConfigurationClass.class})
#WebIntegrationTest
public class MyIntegrationTest {
...
}
In this case, you can create a mock of your service, and tell spring that this service instance has priority:
#Bean
#Primary
public MyService mockService() {
return mock(MyService.class)
}
Doing this, whenever Spring has to inject an unqualified MyService instance, it always chooses your mock. But it doesn't prevent Spring to create the original service.
So inside Spring context there will be two instances of MyService, the mock instance and the original implementation. But using #Primary you're telling Spring that your mock has priority.
On the other hand, in order to prevent Spring to load any mock in your environments (dev, test, etc...), you should annotate your mock configuration class either with a testing #Profile or a custom annotation (like #MockBean) and then configure your component scan strategy to not load this class (#ComponentScan(excludeFilters=#Filter(MockBean.class)))

Resources