【问题标题】:Spring injecting an initiallized mock via constructorSpring通过构造函数注入一个初始化的模拟
【发布时间】:2021-06-15 04:39:46
【问题描述】:

我有一个单例类(私有构造函数),它需要在初始化期间使用 Spring Data 存储库。我有一个作为构造函数参数注入。大致:

@Controller
public class MyClass {
    @Autowired
    private MyClass(MyRepository repo) {
        repo.findAll();
    }
}

我想对我的类进行单元测试,所以我需要使用模拟值初始化一个模拟存储库,然后在我的类初始化之前传递给我的类。如何在 JUnit 测试中编写 Mockito 模拟来实现这一点?

【问题讨论】:

    标签: java spring junit dependency-injection mockito


    【解决方案1】:

    你不需要 Spring;这是构造函数注入的一个优点。只需使用MyRepository mockRepo = mock(MyRepository.class)new MyClass(mockRepo)

    (顺便说一句,您的构造函数应该是公共的。您似乎犯了混淆“单例”的不同含义的常见错误;在 DI 的情况下,它仅意味着 容器创建单个实例并共享它。最后,如果您只有一个构造函数,则不需要@Autowired。)

    【讨论】:

    • 当与真实数据一起使用时,我的类需要一些时间来初始化并且会使用大量的内存,所以我想明确地确保只有一个实例我的课。
    • @KonstantinTarashchanskiy 这是一种严重的代码气味,更有理由清理你的结构;例如,在测试中,您应该能够从 findAll 返回少量生成的项目(还有零个项目!)以全面测试行为。
    • 我同意 chrylis -cautiouslyoptimistic - 公开类中的构造函数,并将 Spring 中的 bean 范围设置为单例。这将使测试变得微不足道,并且仍然为每个 Spring 上下文维护一个实例,这通常意味着每个 JVM 一个实例。如果由于某种原因您绝对不能这样做,请完全删除访问修饰符(使用默认/包访问)并使用 @VisibleForTesting 注释构造函数。您的测试仍然很简单,包含控制器的包之外的任何类都无法调用构造函数。
    【解决方案2】:

    单元测试应该是独立的。这意味着,我们没有使用数据库中的真实数据,甚至没有从我们的测试文件中调用任何服务。

    假设您使用的是 JUni5 并且在您的控制器中有 findAllStudents()。所以你的测试文件大概是这样的

    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    public class MyClassTest {
    
        @Mock
        private MyRepository repo;
        
        @InjectMocks
        private MyClass controller;
    
        @BeforeAll
        public void setup() {
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testDataIsExist() {
            List<String> expectednames = new ArrayList();
            expectedNames.add("Foo");
            expectedNames.add("Bar");
    
            Mockito.when(myRepo.findAll()).thenReturn(expectedNames);
    
            List<String> result = controller.findAllStudents();
            Assertions.assertNotNull(result);
            Assertions.assertEquals(expectednames, result);
        }
    }
    

    所以我们模拟了我们在控制器中使用的所有服务,然后注入到控制器本身。然后在测试方法中,我们模拟repo.findAll() 以返回expectedNames,因此如果控制器找到该函数,它将返回模拟所说的返回内容。

    调用函数后,我们要确保结果符合我们的预期。

    【讨论】:

    • 这确实成功地将我的模拟注入到我的控制器中,但我没有机会在模拟被注入之前对其进行初始化,因此控制器的构造函数看不到模拟数据。跨度>
    【解决方案3】:

    对于@Controller 实现,“单元测试”几乎没有价值。如果你使用@WebMvcTest,那么你可以使用@MockBean

    @WebMvcTest
    class MyControllerTest {
    
      @Autowired
      private MockMvc mockMvc;
    
      @MockBean
      private MyRepository repository;
    
      @Test
      void testSomething() {
        mockMvc.perform( ... );
      }
    }
    

    【讨论】:

    • 为什么我不想对我的控制器进行单元测试?我在其中有非平凡的业务逻辑。在传统的 MVC 模型中,大部分逻辑应该在控制器层。
    • 因为控制器通常有很多注释,你也想测试它们。这是使用@WebMvcTest 最简单的方法。通常控制器中应该有业务逻辑。他们应该只处理 HTTP 转换。我将业务逻辑放在服务层中。
    猜你喜欢
    • 2012-09-15
    • 2017-06-23
    • 2018-07-22
    • 2012-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-22
    相关资源
    最近更新 更多