【问题标题】:how to unit test method using "Spring Data JPA" Specifications如何使用“Spring Data JPA”规范对方法进行单元测试
【发布时间】:2016-05-11 06:36:24
【问题描述】:

我在玩 org.springframework.data.jpa.domain.Specifications,这只是一个基本的搜索:

 public Optional<List<Article>> rechercheArticle(String code, String libelle) {
    List<Article> result = null;

    if(StringUtils.isNotEmpty(code) && StringUtils.isNotEmpty(libelle)){
        result = articleRepository.findAll(Specifications.where(ArticleSpecifications.egaliteCode(code)).and(ArticleSpecifications.egaliteLibelle(libelle)));
    }else{
        if(StringUtils.isNotEmpty(code)){
            result= articleRepository.findAll(Specifications.where(ArticleSpecifications.egaliteCode(code)));
        }else{
            result = articleRepository.findAll(Specifications.where(ArticleSpecifications.egaliteLibelle(libelle)));
        }
    }

    if(result.isEmpty()){
        return Optional.empty();
    }else{
        return Optional.of(result);
    }
}

这实际上工作正常,但我想为此方法编写单元测试,但我不知道如何检查传递给我的 articleRepository.findAll() 的规范

目前我的单元测试看起来像:

@Test
public void rechercheArticle_okTousCriteres() throws FacturationServiceException {
    String code = "code";
    String libelle = "libelle";
    List<Article> articles = new ArrayList<>();
    Article a1 = new Article();
    articles.add(a1);
    Mockito.when(articleRepository.findAll(Mockito.any(Specifications.class))).thenReturn(articles);


    Optional<List<Article>> result = articleManager.rechercheArticle(code, libelle);

    Assert.assertTrue(result.isPresent());
    //ArgumentCaptor<Specifications> argument = ArgumentCaptor.forClass(Specifications.class);
    Mockito.verify(articleRepository).findAll(Specifications.where(ArticleSpecifications.egaliteCode(code)).and(ArticleSpecifications.egaliteLibelle(libelle)));
    //argument.getValue().toPredicate(root, query, builder);


}

有什么想法吗?

【问题讨论】:

  • 你想不通,是因为你不知道怎么模拟articleRepository,还是因为你不知道怎么断言?你的测试目前是什么样的?
  • 我正在使用 Mockito,所以我可以轻松地模拟 articleRepository,但我不知道如何正确断言
  • 我注意到你的问题仍然是“开放的”——因为你没有接受答案。请查看并决定是否要accept 回答。或者让我知道我是否可以做些什么来增强我的输入以使其被接受。接受有助于未来的读者确定问题是否已解决,并对花时间回答你的人表示感谢。谢谢!

标签: java spring unit-testing spring-data spring-data-jpa


【解决方案1】:

我遇到了几乎与您相同的问题,我将包含 Specifications 的类更改为一个对象,而不仅仅是一个具有静态方法的类。这样我可以轻松地模拟它,使用依赖注入来传递它,并测试调用了哪些方法(无需使用 PowerMockito 模拟静态方法)。

如果你想像我一样做,我建议你用集成测试来测试规范的正确性,其余的,只要调用了正确的方法。

例如:

public class CdrSpecs {

public Specification<Cdr> calledBetween(LocalDateTime start, LocalDateTime end) {
    return (root, query, cb) -> cb.between(root.get(Cdr_.callDate), start, end);
}
}

然后你对这个方法做一个集成测试,会测试这个方法是否正确:

@RunWith(SpringRunner.class)
@DataJpaTest
@Sql("/cdr-test-data.sql")
public class CdrIntegrationTest {

@Autowired
private CdrRepository cdrRepository;

private CdrSpecs specs = new CdrSpecs();

@Test
public void findByPeriod() throws Exception {
    LocalDateTime today = LocalDateTime.now();
    LocalDateTime firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth());
    LocalDateTime lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth());
    List<Cdr> cdrList = cdrRepository.findAll(specs.calledBetween(firstDayOfMonth, lastDayOfMonth));
    assertThat(cdrList).isNotEmpty().hasSize(2);
}

现在当你想对其他组件进行单元测试时,你可以这样测试,例如:

@RunWith(JUnit4.class)
public class CdrSearchServiceTest {

@Mock
private CdrSpecs specs;
@Mock
private CdrRepository repo;

private CdrSearchService searchService;

@Before
public void setUp() throws Exception {
    initMocks(this);
    searchService = new CdrSearchService(repo, specs);
}

@Test
public void testSearch() throws Exception {

    // some code here that interact with searchService

    verify(specs).calledBetween(any(LocalDateTime.class), any(LocalDateTime.class));
   // and you can verify any other method of specs that should have been called
}

当然,在 Service 内部,您仍然可以使用 Specifications 类的 whereand 静态方法。

希望对你有帮助。

【讨论】:

    【解决方案2】:

    如果您正在编写单元测试,那么您可能应该使用 MockitoPowerMock 等模拟框架模拟对 articleRepository 类的 findAll() 方法的调用。

    有一个方法verify(),您可以使用该方法检查是否为特定参数调用了模拟。

    例如,如果您正在模拟 articleRepository 类的 findAll() 方法并想知道是否使用特定参数调用此方法,那么您可以执行以下操作:

    Mokito.verify(mymock, Mockito.times(1)).findAll(/* Provide Arguments */);
    

    如果没有为您提供的参数调用 mock,这将导致测试失败。

    【讨论】:

    • 旁注:为什么建议使用 PowerMock?如果你不能用 EasyMock 测试你的代码;那么你应该改变你的代码;而不是转向 PowerMock。
    • 我不建议他开始使用PowerMock。我只是说开始使用一个,不管他觉得舒服。
    • 我不想检查它是否只执行一次我想确定使用了哪些规范:Specifications.where(ArticleSpecifications.egaliteCode(code)).and(ArticleSpecifications.egaliteLibelle( libelle)) 或 Specifications.where(ArticleSpecifications.egaliteCode(code)) 或 Specifications.where(ArticleSpecifications.egaliteLibelle(libelle))
    • 这就是将东西放入不同方法的关键!不要将规范作为文字放置。相反:编写一个生成规范的方法。测试那个方法。然后模拟该方法的结果;只需检查您的方法是否应用该规范生成的结果。这就是单元测试的全部理念:你必须解耦事物。如果你想检查某个东西是否以某种方式被使用;好吧,那么这件事必须“从外面”来;并且您可以提供一个“模拟”来控制内部行为。
    • 如上所述,我同意 OP。这个答案是可以的,因为它建议使用模拟,OP 已将其更新为问题。但是问题仍然存在;如何断言由被测方法创建并传递给模拟的规范是预期的。我喜欢规范,但是一旦构建它们就令人沮丧地不透明。这个问题@Seb 的任何解决方案都会很棒。
    【解决方案3】:

    您的问题是您在一种方法中做了太多事情。您应该有三种不同的方法可以在 articleRepository 上工作。

    然后您可以按照其他人的建议使用模拟:

    • 设置您的模拟,以便您知道应该对 articleRepository 进行哪个调用
    • 验证是否正在发生预期的调用

    请注意:这三个方法应该是内部的;要点是:您不能通过外部的 ONE 调用来测试此方法;因为它做的不止一件事,具体取决于您提供的输入。因此,您需要为代码中的每个潜在路径创建至少一个测试方法。当您将代码分成不同的方法时,这变得更容易(从概念的角度来看)。

    【讨论】:

    • 我实际上是在尝试避免使用三种不同的方法,如果我有 10 个标准而不是两个呢?
    • 那么你的代码中有 10 个路径。忽略那将无济于事。这更清楚地表明您可能会重新设计整个事物。复杂性不会因为将所有东西都集中到一个块中而消失。
    • 我不想只做一个测试,我确实需要 5 个。
    • 我还是完全同意你说的。
    • 我认为方法本身很好,它是规范设计用途的简单示例;动态查询。正如 OP 所说,如果有许多具有多种方法的可选搜索条件是根本不可能的,因为您需要一个查询参数的每个可能组合。动态构建规范的单一方法是正确的。使用模拟来检查规范是否在单元测试中正确构建也是正确的。问题在于,规范 API 似乎无法在规范构建后方便对其进行检查。
    猜你喜欢
    • 2018-01-10
    • 1970-01-01
    • 2017-06-01
    • 2016-06-05
    • 2018-05-07
    • 2017-01-09
    • 2019-05-25
    • 2016-07-22
    • 2019-04-29
    相关资源
    最近更新 更多