【问题标题】:Testing a controller with an auto wired component is null when calling the controller from a test case从测试用例调用控制器时,使用自动连接组件测试控制器为空
【发布时间】:2016-10-06 09:37:01
【问题描述】:

我有一个控制器

@RestController
public class Create {

    @Autowired
    private ComponentThatDoesSomething something;

    @RequestMapping("/greeting")
    public String call() {
        something.updateCounter();
        return "Hello World " + something.getCounter();
    }

}

我有那个控制器的组件

@Component
public class ComponentThatDoesSomething {
    private int counter = 0;

    public void updateCounter () {
        counter++;
    }

    public int getCounter() {
        return counter;
    }
}

我还对我的控制器进行了测试。

@RunWith(SpringRunner.class)
@SpringBootTest
public class ForumsApplicationTests {

    @Test
    public void contextLoads() {
        Create subject = new Create();
        subject.call();
        subject.call();
        assertEquals(subject.call(), "Hello World 2");
    }

}

当控制器调用something.updateCounter() 时测试失败。我得到一个NullPointerException。虽然我知道可以将 @Autowired 添加到构造函数中,但我想知道是否可以使用 @Autowired 字段来执行此操作。如何确保 @Autowired 字段注释在我的测试中有效?

【问题讨论】:

  • 注入一个没有 Spring 的模拟。
  • 你能用代码示例发布答案吗?

标签: java spring junit spring-boot autowired


【解决方案1】:

Spring 不会自动连接您的组件,因为您使用 new 而不是 Spring 来实例化控制器,因此组件没有被实例化

SpringMockMvc 测试检查是否正确:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class CreateTest {
    @Autowired
    private WebApplicationContext context;

    private MockMvc mvc;

    @Before
    public void setup() {
        mvc = MockMvcBuilders
                .webAppContextSetup(context)
                .build();
    }

    @Test
    public void testCall() throws Exception {
        //increment first time
        this.mvc.perform(get("/greeting"))
                .andExpect(status().isOk());
        //increment secont time and get response to check
        String contentAsString = this.mvc.perform(get("/greeting"))
                .andExpect(status().isOk()).andReturn()
                .getResponse().getContentAsString();
        assertEquals("Hello World 2", contentAsString);
    }
}

【讨论】:

    【解决方案2】:

    使用Mockito 并注入您创建的模拟。我更喜欢构造函数注入:

    @RestController
    public class Create {
    
        private ComponentThatDoesSomething something;
    
        @Autowired
        public Create(ComponentThatDoesSomething c) {
            this.something = c;
        }
    }
    

    不要在您的 Junit 测试中使用 Spring。

    public CreateTest {
    
        private Create create;
    
        @Before
        public void setUp() {
            ComponentThatDoesSomething c = Mockito.mock(ComponentThatDoesSomething .class);
            this.create = new Create(c);
        } 
    }
    

    【讨论】:

    • 谢谢。很好的答案!如果不创建自动连接的构造函数,是否没有其他方法可以做到这一点?我有现有的代码,我希望在不进行过多重构的情况下编写测试。
    • 添加构造函数有多难?这不是重构,只是工作。去做吧。
    • 整个问题旨在找出是否可以使用自动装配字段而不是自动装配构造函数编写测试。如果无法做到这一点,您可以更改答案。如果可以,您能否发布另一个代码示例,我会接受。再次感谢您的宝贵时间。
    • 我的答案不会改变。我建议您不要自动装配依赖项,也不要在单元测试中使用 Spring。我基于多年使用 Junit 和 Spring 的个人经验。我不在乎你接受什么。你可以听我的建议,也可以不听。
    • 调用“new”意味着 Spring 不适合您创建的对象。它不再负责自动装配不受其控制的任何 bean。这就是您获得 NPE 的原因。我不喜欢使用 Spring,因为它迫使我引入所有这些依赖项。使用我注入的模拟,可以防止我对单个类的单元测试变成与我的整个对象图的集成测试。
    【解决方案3】:

    @Autowired 类可以使用 MockitoJUnitRunner 使用正确的注释轻松模拟和测试。

    有了这个,你可以对单元测试的模拟对象做任何你需要做的事情。

    这是一个快速示例,它将使用来自 ComponentThatDoesSomething 的模拟数据测试 Create 方法调用。

    @RunWith(MockitoJUnitRunner.class)
    public class CreateTest {
    
        @InjectMocks
        Create create;
        @Mock
        ComponentThatDoesSomething componentThatDoesSomething;
    
        @Test
        public void testCallWithCounterOf4() {
    
            when(componentThatDoesSomething.getCounter()).thenReturn(4);
            String result = create.call();
            assertEquals("Hello World 4", result);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多