Spring - 如何正确使用@Autowired 来防止控制器/ MockMvc 为空?

Posted

技术标签:

【中文标题】Spring - 如何正确使用@Autowired 来防止控制器/ MockMvc 为空?【英文标题】:Spring - How to properly use @Autowired to prevent controller / MockMvc from being null? 【发布时间】:2019-05-21 13:01:06 【问题描述】:

我正在尝试运行一些单元测试并遇到一个问题,我确信这源于对自动装配的误解。我有一个单元测试类,我试图在 MockMvc 和 REST 控制器上使用 @Autowired——两者最终都为空。

我看到一些消息来源试图解释为什么会发生这种情况(包括这个More of Less post 和一个有用的*** post,它给了我一些见解但并没有完全帮助我解决我的问题)。

以下是我为重现此问题而制作的示例项目的相关源代码。

ManagerControllerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest(ManagerController.class)
public class ManagerControllerTest 
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private Manager manager;

    @Autowired
    private ManagerController controller;

    @Test
    public void controllerNotNull() throws Exception 
        assertThat(controller).isNotNull();
    

    @Test
    public void testStoreSomething() throws Exception 
        String path = "/manager/store-something/";

        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(path)
                    .characterEncoding("UTF-8")
                    .contentType(MediaType.APPLICATION_JSON);

        MvcResult result = mockMvc.perform(builder).andReturn();
        assertEquals(HttpStatus.CREATED, result.getResponse().getStatus());
    

controllerNotNull() 测试结果在

java.lang.AssertionError: 期望实际不为空

虽然奇怪的是,当我在 Java 中创建一个新的 Gradle 项目并将我的示例代码从这篇文章导入其中时,controllerNotNull() 通过了。

testStoreSomething() 的结果是

java.lang.NullPointerException 在 com.example.sandbox.rest.ManagerControllerTest.testStoreSomething(ManagerControllerTest.java:46)

问题就在这里:我误解了什么?我究竟做错了什么?我可以从控制器中删除 @Autowired 并使用 new ManagerController() 实例化它,但我留下了 MockMvc 问题。

ManagerController.java

@Controller
@RequestMapping(value = "/manager/")
public class ManagerController 
    Manager manager = new Manager(new StringStorage());

    @PostMapping(value = "store-something")
    private ResponseEntity<?> storeSomething(String str) 
        manager.storeSomething(str);
        return new ResponseEntity<>(CREATED);
    

Manager.java

public class Manager 
    private final Storage storage;

    public Manager(Storage storage) 
        this.storage = storage;
    

    public void storeSomething(String str) 
        storage.store(str);
    

Storage.java

public interface Storage 
    void store(String str);

StringStorage.java

public class StringStorage implements Storage 
    Map<String, String> stringMap;

    @Override
    public void store(String str) 
        stringMap.put(str, str);
    
 

Application.java

@SpringBootApplication
public class Application 

    public static void main(String[] args) 
        SpringApplication.run(Application.class, args);
    


build.gradle 已从原始帖子中编辑(使用 JUnit4),但问题仍然存在。

repositories 
    jcenter()


apply plugin: 'java'
apply plugin: 'eclipse'

dependencies 
 compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.0.0.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-tomcat', version: '2.0.0.RELEASE'

    testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '2.0.0.RELEASE'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:4.0.0'
    testImplementation 'org.junit.jupiter:junit-jupiter-params:4.0.0'
    testCompile group: 'org.mockito', name: 'mockito-core', version: '2.17.0'
    testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: "4.0.0"
    testCompile group: 'org.junit.platform', name: 'junit-platform-launcher', version: "1.3.1"

【问题讨论】:

不确定确切答案,但请查看docs.spring.io/spring-boot/docs/current/api/org/springframework/… 并阅读可能对您有所帮助的文档。在我的测试中,我使用 @ContextConfiguration(classes = Repository.class, TestConfiguration.class) 以便我可以将 Repository 实例自动连接到我的测试中 把Manager.class和StorageService.class改成spring组件就好了!否则请检查她以了解有关使用 mockmvc 进行弹簧测试的更多信息:spring.io/guides/gs/testing-web 对于初学者来说,从你的控制器中删除Manager的构造并在字段上使用@Autowired(或者更确切地说使用更好的构造函数注入)。您的经理类应该使用 @Component 注释,就像我们的 StringStorage bean 一样。虽然后者并不是真正必要的。接下来,您将在测试中使用 JUnit4,但您的所有依赖项都是针对 Junit5 的。因此,要么更改您的测试,要么使用 JUnit4 作为依赖项。最后,您还需要在 Gradle 构建中使用 spring-boot 插件(恕我直言,它缺少某些部分)。 【参考方案1】:
@RestController
@RequestMapping(value = "/manager")
public class ManagerController 

    @Autowired
    Manager manager;

    @PostMapping(value = "/store-something")
    private ResponseEntity<?> storeSomething(String str) 
        manager.storeSomething(str);
        return new ResponseEntity<>(CREATED);
    



@Component
public class Manager 

    @Autowired
    private Storage storage;

    public void storeSomething(String str) 
        storage.store(str);
    



public interface Storage 
    void store(String str);


@Service
public class StringStorage implements Storage 
    Map<String, String> stringMap = new HashMap<>();

    @Override
    public void store(String str) 
        stringMap.put(str, str);
    
 




@SpringBootApplication
public class Application 

    public static void main(String[] args) 
        SpringApplication.run(Application.class, args);
    


测试类:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ApplicationTest 

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldReturnDefaultMessage() throws Exception 
        this.mockMvc.perform(post("/manager/store-something")).andDo(print()).andExpect(status().isOk())
                .andExpect(content().string(containsString("Hello World")));
    

【讨论】:

【参考方案2】:

将注解 AutoConfigureMockMvc 添加到您的测试类

@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
public class ManagerControllerTest 
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private Manager manager;

    @Autowired
    private ManagerController controller;

    @Test
    public void controllerNotNull() throws Exception 
        assertThat(controller).isNotNull();
    

    @Test
    public void testStoreSomething() throws Exception 
        this.mvc.perform(get("/manager/store-something/"))
        .contentType(MediaType.APPLICATION_JSON)
        .andExpect(status().isOk())

    

或者,您也可以将 TestRestTemplate 与 @SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) 一起使用

【讨论】:

以上是关于Spring - 如何正确使用@Autowired 来防止控制器/ MockMvc 为空?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Autowired空指针异常

如何在 Spring Boot 中手动新建实例中使用 @Autowired

在 Spring 中使用 @Autowired 注解时出现以下错误应该如何解决?

Spring MVC - @Autowired 如何工作?

如何在spring boot的多个类中使用@Autowired MongoTemplate

Spring @Autowired 和 @Qualifier [关闭]