【问题标题】:How to test a controller with constructor injection by MockMvc如何通过 MockMvc 使用构造函数注入测试控制器
【发布时间】:2017-03-19 08:28:57
【问题描述】:

我有一个带有构造函数注入的控制器

@RestController
@RequestMapping("/user")
public class MainController {

    private final UserMapper userMapper; // autowired by constructor below

    public MainController(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @RequestMapping("/getChannels")
    public String index() {
        LoginUser user = userMapper.getUserByName("admin");
        return "Channels: " + user.getChannels();
    }
}

这是一个运行良好的简单类。但是,当我尝试使用以下类运行 JUnit 测试时,出现错误。

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class MainControllerTest {

    private MockMvc mvc;
    private final UserMapper userMapper;

    public MainControllerTest(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Before
    public void setUp() throws Exception {
        mvc = MockMvcBuilders.standaloneSetup(new MainController(userMapper)).build();
    }

    ......

错误是:

java.lang.Exception: Test class should have exactly one public zero-argument constructor

我对上面的错误消息感到困惑,我怎么能用零参数构造函数注入 userMapper? 我知道可以在 MainController 中为 userMapper 添加@Autowired,但是不建议使用字段注入。 请任何人指导我一种适合构造函数注入和 MockMvc 测试的方法。谢谢。

【问题讨论】:

    标签: java spring-junit


    【解决方案1】:

    其他答案谈论使用注释,但在这里您的问题与使用注释没有任何关系。请记住,作为 spring 4.3+,您不需要为依赖项注释构造函数,see more here

    事实上,您不需要尝试在您的测试类中模拟构造函数注入 (MainControllerTest)。 您只需要在应用程序上下文中将UserMapper 声明为弹簧组件,在您的测试类中,它将作为正在运行的应用程序自动注入到您的控制器中。

    您的错误是什么意思:错误消息中的所有 Junit Test 类应该只有一个公共零参数构造函数,这是因为在您的场景这样的情况下,Junit Test 套件不知道如何实例化测试类。

    【讨论】:

    • 我这里还有一个疑问。有没有办法在不使用'new'关键字或@Autowired注解的情况下在测试类中获取MainController实例?
    【解决方案2】:

    在现场使用@Autowire。在生产代码中不建议使用字段注入,因为它会使推理变得混乱(在哪里注入什么以及为什么注入)。但是测试环境(尤其是像这样的环境)很简单,因此不存在硬推理的陷阱。

    @Autowired
    private final UserMapper userMapper;
    
    public MainControllerTest() { //remove me, implicit
    }
    

    此更改将解决问题,因为 jUnit 将拥有一个它可以理解的构造函数,并且能够实例化测试类。

    【讨论】:

      【解决方案3】:

      您缺少 @AutowiredMainController 构造函数,并始终使用构造函数注入来解决问题,如下所示:

      @RestController
      @RequestMapping("/user")
      public class MainController {
      
          private final UserMapper userMapper;
      
          @Autowired
          public MainController(UserMapper userMapper) {
              this.userMapper = userMapper;
          }
      
          //add methods here
      }
      

      使用字段注入不是最佳做法,请查看here 了解更多详情。

      另外,要解决此问题,您需要更新您的测试类,如下所示:

      @RunWith(SpringJUnit4ClassRunner.class)
      @WebAppConfiguration
      public class MainControllerTest {
      
          private MockMvc mvc;
      
          @Mock
          private UserMapper userMapper;
      
          @InjectMocks
          private MainController mainController = new MainController(userMapper);
      
          @Before
          public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
              mvc = MockMvcBuilders.standaloneSetup(mainController)..build();
          }
      
          ......
      }
      

      【讨论】:

      • 我测试了你建议的源代码。然后我在 MainController 中得到了一个 NPE。这是由@InjectMocks 生成的 userMapper 不起作用引起的。无论如何,我找到了一个解决方案,用 SpringBootTest 替换 WebAppConfiguration 的注解。但是我仍然想知道如何使它在没有 Spring Boot 的情况下可以使用。
      • 投反对票,因为代码不起作用。 NPE 在初始化测试中的 mainController 字段时迫在眉睫。该字段不应手动分配,因为 userMapper 字段在此位置仍为空。它稍后在 setUp() 方法中由 initMocks(this) 初始化。因此,一般规则是如果字段上存在@InjectMocks,则不要手动分配该字段。注入所有模拟依赖项并提供实例化对象是 Mockito 的职责。更不用说tedvinke.wordpress.com/2014/02/13/…
      • Marcin 是对的。特别是当您通过构造函数传递依赖项时,上述代码不起作用。您需要按照 Marcin 给出的链接进行操作。 1. RunWith(MockitoJUnitRunner.class) 2. 从控制器中删除 InjectMocks。实际上,控制器不需要注释。 3.在一个带有Before注解的函数中初始化控制器。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-08
      • 1970-01-01
      相关资源
      最近更新 更多