简体   繁体   English

如何使用“Spring Data JPA”规范进行单元测试方法

[英]how to unit test method using “Spring Data JPA” Specifications

I was playing with org.springframework.data.jpa.domain.Specifications, it's just a basic search : 我正在玩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);
    }
}

And that's actually working fine but I'd like to write unit tests for this method and I can't figure out how to check specifications passed to my articleRepository.findAll() 这实际上工作正常,但我想为这个方法编写单元测试,我无法弄清楚如何检查传递给我的articleRepository.findAll()的规范

At the moment my unit test looks like : 目前我的单元测试看起来像:

@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);


}

Any idea? 任何想法?

I was having almost the same problems as you had, and I changed my class that contains Specifications to be an object instead of just one class with static methods. 我遇到了几乎和你一样的问题,并且我将包含规范的类更改为一个对象而不是一个静态方法的类。 This way I can easily mock it, use dependency injection to pass it, and test which methods were called (without using PowerMockito to mock static methods). 这样我就可以轻松地模拟它,使用依赖注入来传递它,并测试调用哪些方法(不使用PowerMockito来模拟静态方法)。

If you wanna do like I did, I recommend you to test the correctness of specifications with integration tests, and for the rest, just if the right method was called. 如果你想像我一样做,我建议你用集成测试测试规范的正确性,其余的,只要调用正确的方法。

For example: 例如:

public class CdrSpecs {

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

Then you have an integration test for this method, which will test whether the method is right or not: 然后,您对此方法进行了集成测试,该测试将测试该方法是否正确:

@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);
}

And now when you wanna unit test other components, you can test like this, for example: 现在,当您想要对其他组件进行单元测试时,您可以像这样进行测试,例如:

@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
}

And of course, inside the Service you can still use the where and and static methods of Specifications class. 当然,在服务内部,您仍然可以使用规范类的where static方法。

I hope this can help you. 我希望这可以帮到你。

If you are writing Unit Tests then you should probably mock the call to findAll() method of articleRepository Class using a mocking framework like Mockito or PowerMock . 如果你正在编写单元测试,那么你或许应该嘲笑到呼叫findAll()的方法articleRepository使用像嘲弄的框架类MockitoPowerMock

There is a method verify() using which you can check if the mock is invoked for the particular parameters. 有一个方法verify() ,您可以使用它来检查是否为特定参数调用了mock。

For Example, if you are mocking the findAll() method of articleRepository Class and want to know if this method is called with particular arguments then you can do something like: 例如,如果您正在articleRepository类的findAll()方法,并想知道是否使用特定参数调用此方法,那么您可以执行以下操作:

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

This will fail the test if mock has not been called for the arguments that you provided. 如果没有为您提供的参数调用mock,则测试将失败。

Your problem is that you are doing too many things within that one method. 你的问题是你在这个方法中做了太多事情。 You should have three different methods that work on articleRepository. 您应该有三种不同的方法可以在articleRepository上运行。

Then you can use mocking as the others suggest: 然后你可以像其他人建议的那样使用模拟:

  • setup your mocks so that you know which call on articleRepository should be made 设置你的模拟,以便你知道应该在articleRepository上调用哪个
  • verify that exactly the expected calls are happening 验证确切的预期呼叫正在发生

Please note: these three methods should be internal; 请注意:这三种方法应该是内部的; the main point there is: you can't test this method with ONE call from the outside; 主要观点是:你无法通过外部的一个电话来测试这种方法; as it is doing more than one thing, depending on the input that you provide. 因为它不止一件事,取决于您提供的输入。 Thus you need to create at least one test method for each of the potential paths in your code. 因此,您需要为代码中的每个潜在路径创建至少一个测试方法。 And that becomes easier (from a conceptual point of view) when you separate your code into different methods. 当您将代码分成不同的方法时,这会变得更容易(从概念的角度来看)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM