【问题标题】:How to deal with Page while unit testing with mockito and Junit如何在使用 mockito 和 Junit 进行单元测试时处理 Page
【发布时间】:2022-01-16 04:50:38
【问题描述】:

我想用假数据模拟数据库调用,但我陷入了以下场景:

我的服务

public class MyService {
    // some stuff

    Page<SampleDto> sample = repo.findAllSample();

    // other stuff
}

我想用when() 存根,但我无法做到这一点。我的测试是: 我的服务测试

public class MySampleTest {

    @Test
    void myTest() {
        // initialisation and all stuff

        when(myRepo.findAll()).thenReturn(......)
        // I want to pass 2 fake SampleDto from 
        // here but don't know how to do that
    }
}

【问题讨论】:

    标签: java unit-testing junit mocking mockito


    【解决方案1】:

    解决此类任务通常有两个主要方向:

    1. 假对象
    2. 嘲讽

    Fake objects 方法是您的 repo 的简单实现,无需任何第三方库,用于轻量级/简单案例:

    public class FakeRepo implements Repo<T> {
       private final Collection<T> all;
    
       public FakeRepo(){
          this(Collections.emptySet());
       }
    
       public FakeRepo(Collection<T> all){
           this.all = all;
       }
    
       @Override
       public Collection<T> findAll(){
           return this.all;
       }
    }
    

    在你的测试中可能看起来像

    @Test
    public void justdoit(){
       MyService service = new MyService(
           new FakeRepo(Arrays.asList(1,2))
       );
       // test the service & methods
    }
    

    Mocking 允许您制作更复杂的解决方案。 请注意方法Mockito.mock(ArrayList.class)Mockito.spy(new ArrayList&lt;String&gt;())。您需要使用 Mockito 引擎来组装复杂的对象,例如 https://www.baeldung.com/mockito-annotations

    @Test
    public void whenNotUseMockAnnotation_thenCorrect() {
        List mockList = Mockito.mock(ArrayList.class);
        
        mockList.add("one");
        Mockito.verify(mockList).add("one");
        assertEquals(0, mockList.size());
    
        Mockito.when(mockList.size()).thenReturn(100);
        assertEquals(100, mockList.size());
    }
    

    @Test
    public void whenNotUseSpyAnnotation_thenCorrect() {
        List<String> spyList = Mockito.spy(new ArrayList<String>());
        
        spyList.add("one");
        spyList.add("two");
    
        Mockito.verify(spyList).add("one");
        Mockito.verify(spyList).add("two");
    
        assertEquals(2, spyList.size());
    
        Mockito.doReturn(100).when(spyList).size();
        assertEquals(100, spyList.size());
    }
    

    【讨论】:

    • 谢谢@lazylead 我很欣赏你的模拟和间谍解决方案。
    【解决方案2】:

    只需创建您想要返回的内容并将其传递给thenReturn()

    在您的情况下,它可能如下所示:

    // I making this up, because you did not state what 'Page' actually is
    var result = new Page();
    result.add(new SampleDto(1));
    result.add(new SampleDto(2));
    when(myRepo.findAllSample()).thenReturn(result);
    

    您可能无法执行此操作,具体取决于 Page 的实际实现,例如,如果这是一个 JPA Page。在这种情况下,无论如何您都不应该真正将Page 返回到您的服务中,而是将JPA 存储库包装到一个适配器存储库中,该存储库返回List&lt;SampleDto&gt;Set&lt;SampleDto&gt;。否则,属于域的服务将依赖于 JPA 等基础设施代码的实现细节,这很少是一个好主意。

    【讨论】:

      【解决方案3】:

      如果需要在服务类中处理一些其他功能,您也可以模拟 Page 对象。假设您的服务类实际上是这样的:

      public class MyService {
          @Resource
          private SampleRepository repo;
      
          public Page<SampleDto> findAllSample() {
              var sampleData = repo.findAllSample();
              // perhaps some operations with sample data and page
              return sampleData;
          }
      }
      

      那么测试类可能类似于 (JUnit4):

      @RunWith(MockitoJUnitRunner.class)
      public class MySampleTest {
          @Mock
          private SampleRepository repo;
          @Mock
          private Page<SampleDto> page;
          @InjectMocks
          private MyService myService;
      
          @Test
          public void test() {
          doReturn(page).when(repo).findAll();
          var listSampleDtos = List.of(new SampleDto()); // populate with what you need
          doReturn(listSampleDtos).when(page).getContent();
          var page = myService.findAllSample();
          // ... do the asserts, just as an example:
          verify(repo).findAll(); // called
          assertEquals(1, page.getContent().size());
          }
      

      【讨论】:

      • 正如我已经评论过 Ravan 的回答:让模拟返回其他模拟是一个坏主意。
      • @EricSchaefer 好吧,也许吧。从来没有任何问题。使用 @Mock@Spy 没有任何问题,因为模拟模拟了答案。如果您可以链接一些资源以查看可能的问题,会很高兴吗?它会以某种方式混合 mockito 吗?
      • 我手头没有链接。这也是一个更普遍的观察。模拟返回模拟通常会导致奇怪的错误情况(尤其是在使用模拟库时),因为涉及所有“魔法”。除此之外,通常暗示总体设计可能需要改进。例如您要模拟 Page 的唯一原因是,它是一种难以使用的外部类型(即因为它是 JPA)。这也意味着该服务甚至不应该知道 Page。包装 JPA 存储库并让包装器返回 List 或 Set 而不是 JPA-Page。
      • 感谢@pirho 对上述问题的出色而深入的解释。我得到了我想要的谢谢你分享你的知识。
      【解决方案4】:

      您可以模拟页面并制作示例对象。可能这就是您正在搜索的确切答案。

      我的样品测试

      public class MySampleTest {
      
      @Test
      void myTest() {
      
          // initialisation and all stuff
      
          List<SampleDto> sampleDtos = new ArrayList<>();
      
          //now add any number of sample dto
          dtos.add(new SampleDto());
      
          //mock page class here
          Page page = mock(Page.class);
      
          //stub page.getContent
           when(page.getContent()).thenReturn(Collections.singletonList(sampleDtos));
      
          // No need to do this  when(myRepo.findAll()).thenReturn()
        
      }
      

      }

      【讨论】:

      • 一般来说,让模拟返回其他模拟是个坏主意。如果测试代码中出现任何问题,您会很高兴找到原因。
      猜你喜欢
      • 1970-01-01
      • 2020-01-17
      • 1970-01-01
      • 2019-12-28
      • 2015-07-26
      • 2017-09-19
      • 2019-04-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多