Search code examples
spring-bootspring-data-jpajunit5spring-test

Facing Null-Pointer error on message source


this is my juntt 5 controller test cases class

@ExtendWith(SpringExtension.class)
@WebMvcTest(DomainController.class)
class DomainControllerTest {
  @Autowired private MockMvc mockMvc;
  @MockBean private DomainHandler domainHandler;


  @Test
  void save() throws Exception {
    when(domainHandler.save(any(DomainRequest.class))).thenReturn(createBaseResponseForSave());

    final var requestBuilder =
        post("/api/domain")
            .contentType(MediaType.APPLICATION_JSON)
            .content(DomainTestData.saveRequestJson());

    mockMvc
        .perform(requestBuilder)
        .andExpect(status().isOk())
        .andExpect(content().json(DomainTestData.saveResponseJson()))
        .andReturn();
  }
}

this is my data creation class

public class DomainTestData {

  public static Domain domain = createDomain();
  public static final List<Domain> domainList = createDomainList();

  private static MessageSource messageSource;

  public static String getMessage(String key) {
    return messageSource.getMessage(key, null, Locale.getDefault());
  }

  public static BaseResponse createBaseResponseForSave() {
    String successMessage = getMessage("api.success.response");
    return new BaseResponse<>(
            1001,
        successMessage,
            new DomainResponse("Java", "bb11f2a9-a38a-45f5-92a2-3dbcefe8cb04")
    );
  }

  public static String convertObjectToFormattedJson(Object dataObject)
      throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(dataObject);
  }

  public static String saveResponseJson() throws JsonProcessingException {
    return convertObjectToFormattedJson(createBaseResponseForSave());
  }
}

this is my MessageSource Configration class in

exam-engine-core/src/main/java/com/testiqo/exam/engine/config/MessagePropertiesConfiguration.java
@Configuration
public class MessagePropertiesConfiguration {

  @Bean
  public MessageSource messageSource() {
    ReloadableResourceBundleMessageSource messageSource =
        new ReloadableResourceBundleMessageSource();
    messageSource.setBasenames(
        "classpath:/messages/api_error_messages",
        "classpath:/messages/api_response_messages",
        "classpath:messages");
    messageSource.setDefaultEncoding("UTF-8");
    return messageSource;
  }

  @Bean
  public LocalValidatorFactoryBean getValidator() {
    LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
    bean.setValidationMessageSource(messageSource());
    return bean;
  }
}

this is my api_response_messages.properties file in

exam-engine-core/src/main/resources/messages/api_response_messages.properties
api.success.response=Success
api.successfully.saved.response={0} : {1} is saved successfully
api.successfully.updated.response={0} : {1} is updated successfully
api.successfully.deleted.response={0} is deleted successfully
api.not.found={0} not found
api.no.data.found=No data found for {0}
api.no.data.found.for.uid=No {0} found for given uid {1}
api.no.data.found.for.given.search.criteria=No data found for given search criteria
api.question.successfully.added.exam.response = Question successfully added in exam
api.exam.result.fetch = The exam result has been successfully fetched.
api.question.successfully.removed.exam.response = The question has been successfully removed from the exam
api.exam.view.response = The exam details have been fetched successfully
exam.not.found= {0} details not found for {1}
domain.not.found= {0} details not found for {1}
question.domain.not.found = Topic does not exist in domain

when i run my test cases facing null-pointer error on MessageSource

This is my error

Cannot invoke "org.springframework.context.MessageSource.getMessage(String, Object[], java.util.Locale)" because "com.testiqo.exam.engine.utils.domain.DomainTestData.messageSource" is null
java.lang.NullPointerException: Cannot invoke "org.springframework.context.MessageSource.getMessage(String, Object[], java.util.Locale)" because "com.testiqo.exam.engine.utils.domain.DomainTestData.messageSource" is null
    at com.testiqo.exam.engine.utils.domain.DomainTestData.getMessage(DomainTestData.java:29)
    at com.testiqo.exam.engine.utils.domain.DomainTestData.createBaseResponseForSave(DomainTestData.java:33)
    at com.testiqo.exam.engine.controller.DomainControllerTest.save(DomainControllerTest.java:47)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
    at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
    at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
    at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)

I need to use MessageSource for get message so how to use MessageSource


Solution

  • I supposed, you've got this NPE because the class DomainControllerTest calls the static method createBaseResponseForSave() from the class DomainTestData that use the static class member messageSource. The NPE occures because Spring cannot inject the bean messageSource into a static context.

    To avoid the NPE, it's enough for you to just make the class DomainTestData non-static -> make class member messageSource non-static which will allow Spring to inject the bean messageSource through a constructor.

    Also you can try to define the bean messageSource as a static bean and this can help too. But I'm not sure it will work.

    UPD

    Related changes:

    The first change is to make the DomainTestData class non-static. To do this, youd need to remove the static special word in the line private static MessageSource messageSource; and do the same for two methods: getMessage(...) and createBaseResponseForSave(). The next step is to create a constructor for this class as follows:

    public DomainTestData(MessageSource messageSource) {
        this.messageSourcec = messageSource;
    }
    

    The second change is to tell Spring to inject the messageSource bean into the DomainTestData class. To do this it's enough to add the @Component annotation above the class declaration as follows:

    @Component
    public class DomainTestData {
       // ...
    }
    

    And the final step is to inject the changed DomainTestData class into the DomainControllerTest and change the static method call of this class to a call via class-member as follows:

    class DomainControllerTest {
        // ...
        @Autowired private DomainTestData domainTestData;
        // ...
        
        @Test
        void save() throws Exception {
             when(domainHandler.save(any(DomainRequest.class))).thenReturn(domainTestData.createBaseResponseForSave());
             // ...
        }