来自 .properties 文件的 Spring Boot 单元测试 @Value 给出 NullPointerException

Posted

技术标签:

【中文标题】来自 .properties 文件的 Spring Boot 单元测试 @Value 给出 NullPointerException【英文标题】:Spring Boot Unit Test @Value from .properties File gives NullPointerException 【发布时间】:2020-01-23 12:49:04 【问题描述】:

我正在尝试从属性文件中读取 Spring Boot 中单元测试用例的值。我有两个config.properties 文件,一个在src/main/resources

prop = some-value

还有一个src/test/resources

prop = some-test-value

主要应用类:

package company.division.project;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.PropertySource;

@SpringBootApplication(scanBasePackages = "company.division.project")
@PropertySource(value = "classpath:config.properties")
public class Application extends SpringBootServletInitializer 
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) 
        System.setProperty("DUMMY_PROPERTY", "dummy-value");

        return application.sources(Application.class);
    

    public static void main(String[] args) throws Exception 
        // Do nothing with main
    

要测试的服务类:

package company.division.project.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class Service 
    @Autowired
    Environment environment;

    public String getProperty() 
        return environment.getProperty("prop");
    


ServiceTest 类。我尝试了两种方法来检索src/test/resources/config.properties 文件中的值;一种带有@Autowired Environment,另一种带有@Value 注释......两者都不起作用:

package company.division.project.service;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.test.context.TestPropertySource;

@RunWith(MockitoJUnitRunner.class)
@TestPropertySource("classpath:config.properties")
public class ServiceTest 
    @InjectMocks
    Service service;

    @Autowired
    Environment environment;

    @Value("$prop")
    private String expectedProperty;

    @Test
    public void testGetPropertyWithValueAnnotation() 
        assertEquals(expectedProperty, service.getProperty());
    

    @Test
    public void testGetPropertyWithEnvironment() 
        assertEquals(environment.getProperty("prop"), service.getProperty());
    

我在 *** 上的某个地方读到,为了在 Spring 测试类中自动连接组件,我需要为测试创建一个完整的上下文,所以我尝试了这个(更改注释和测试运行器):

package company.division.project.service;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest 
    @InjectMocks
    Service service;

    @Autowired
    Environment environment;

    @Value("$prop")
    private String expectedProperty;

    @Test
    public void testGetPropertyWithValueAnnotation() 
        assertEquals(expectedProperty, service.getProperty());
    

    @Test
    public void testGetPropertyWithEnvironment() 
        assertEquals(environment.getProperty("prop"), service.getProperty());
    

上下文已创建,但两种方法再次以NullPointerExceptions 结束。

【问题讨论】:

@RunWith(MockitoJUnitRunner.class) 不起作用。确切的异常消息是什么? 这是错误信息(在所有情况下):java.lang.NullPointerException at company.division.project.service.ServiceTest.testGetPropertyWithValueAnnotation(ServiceTest.java:34) 为什么@RunWith(MockitoJUnitRunner.class) 不起作用? 【参考方案1】:

您的测试的问题是您试图以错误的方式使用MockitoJUnitRunner.class

如果您使用@InjectMocks 模拟服务,则需要确保需要通过模拟服务调用来返回值Service.getProperty()。如果您使用的是SpringRunner.class,那么您不应该有@InjectMocks,但应该有@Autowired 用于服务。以下测试工作。

@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest 
    @Autowired
    Service service;

    @Autowired
    Environment environment;

    @Value("$prop")
    private String expectedProperty;

    @Test
    public void testGetPropertyWithValueAnnotation() 
        assertEquals(expectedProperty, service.getProperty());
    

    @Test
    public void testGetPropertyWithEnvironment() 
        assertEquals(environment.getProperty("prop"), service.getProperty());
    


【讨论】:

这在我在这里发布的最小示例中完美运行,但是当我在我的原始代码上尝试它时,它给了我一个IllegalStateException: Unable to find a @SpringBootConfiguration。有什么想法吗? @shinvu @SpringBootTest 搜索由@SpringBootApplication. 注释的类。您可以通过@SpringBootTest(classes=YourMainClass.class) 明确地提供您的主类,或者将您的测试放在您的 SpringBootApplication 注释类的相同或子包中。 @helospak 完美!添加主类显式工作。谢谢。【参考方案2】:

感谢@shazin 的回答和我自己的一些研究,我已经能够解决这个问题。

基本上,@RunWith 中指定的测试运行器类与 Mockito 模拟的注释之间需要兼容。我们要测试Service 类:

服务类

@Component
public class Service 
    @Autowired
    Environment environment;

    public String getProperty() 
        return environment.getProperty("prop");
    

如果您使用@RunWith(MockitoJUnitRunner.class),则可以使用@InjectMocks@Mock 注释,如下所示。无论Service 中的@Autowired 是什么,都将自动与模拟连接:

使用MockitoJUnitRunner测试类

@RunWith(MockitoJUnitRunner.class)
public class ServiceTest 
    @InjectMocks
    Service service;
        @Mock
        Environment mockEnvironment;

    @Before
    public void before() 
        Mockito.when(mockEnvironment.getProperty("prop")).thenReturn("some-test-value")
    

但是您不能在测试类本身中自动连接任何东西。这需要一个 Spring 上下文(需要一个 Spring 上下文来管理自动连接到对象中的 bean)。这就是@RunWith(SpringRunner.class) 出现的地方。您可以使用它来运行带有专用 Spring 上下文的测试用例(您会注意到测试用例日志显示为每个带有 @RunWith(SpringRunner.class) 注释的测试类启动了一个新的 Spring 应用程序)。您还需要使用 @SpringBootTest 注释提供配置详细信息。

需要注意的是,带有@RunWith(SpringRunner.class) 的测试类将无法理解@InjectMocks@Mock 注释;您必须使用 @MockBean 注释。这将通过用模拟替换 bean 来有效地修改 Spring 上下文;任何带有 @Autowired 注释的东西都会自动与模拟 bean 自动连接:

使用SpringRunner测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes=Application.class)
public class ServiceTest 
    @Autowired
    Service service;

    @MockBean
    Environment mockEnvironment;

    @Before
    public void before() 
        Mockito.when(mockEnvironment.getProperty("prop")).thenReturn("some-test-value")
    

所以...使用 @RunWith(SpringRunner.class) 除了更改注释的名称(@InjectMocks -> @Autowired@Mock -> @MockBean)之外没有任何效果,对吧?错误的。使用SpringRunner 可以让您在您的测试用例中 自动装配组件。因此,如果您想使用实际的Environment(不是模拟的),您也可以这样做;只需从专用的 Spring 上下文中自动连接它:

使用SpringRunner@Autowired 环境的测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes=Application.class)
public class ServiceTest 
    @Autowired
    Service service;

    @Autowired
    Environment environment;

    @Test
    public void testServiceGetProperty() 
        assertEquals(environment.getProperty("prop"), service.getProperty("prop");
    


这样就解决了问题。

【讨论】:

以上是关于来自 .properties 文件的 Spring Boot 单元测试 @Value 给出 NullPointerException的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 集成测试无法访问 application.properties 文件

Java Spring - 如何通过@WithUserDetails 使用来自application.properties 的值

来自属性文件的 Spring @Scheduled cron 详细信息 - 异常

Spring boot application.properties和 application.yml 初学者的学习

spring.properties文件在哪儿

idea在处理spring国际化解决中文乱码,properties的格式:native-to-ascii