Spock 测试中未返回存根对象方法值

问题描述

我是 Spock 框架的新手,正在编写一个测试用例,其中我试图模拟一个名为 QueryDatabase 的类

public class QueryDatabase {

    public BigInteger countRecords(Instant start,Instant end) {

        Flux<CountRecord> countValue = query("select * from users");
        Optional<Object> value = Optional.ofNullable(countValue.blockFirst()).map(CountRecord::getValue);
        BigInteger count = value.filter(BigInteger.class::isinstance)
                    .map(BigInteger.class::cast).orElse(BigInteger.valueOf(0));

        return count
    }
    
    public Flux<CountRecord> query(String query) {
    
    }
}

但是下面的测试用例是检查 countRecords(Instant,Instant) 返回的值总是给出 0,所以这意味着在 when 部分返回的值

recordCount.query(_) >> Flux.just(CountRecord.builder().value(new BigInteger(133)).build())

没有被使用,似乎 recordCount.query(_) >> Flux.empty() 也没有任何影响,它总是返回认的 BigInteger 值 0

def "record count"() {

        given:
        def Now = Instant.Now()
        def last10Minutes = Now.minus(10,ChronoUnit.MINUTES);
        def recordCount = Stub(QueryDatabase)

        when: "query returning empty flux"
        recordCount.query(_) >> Flux.empty()

        then:
        recordCount.countRecords(last10Minutes,Now) == 0

        when: "query returning the count record"
        recordCount.query(_) >> Flux.just(CountRecord.builder().value(new BigInteger(133)).build())

        then:
        recordCount.countRecords(last10Minutes,Now) == 133

 }

在这里做错了什么吗?

解决方法

您的代码有几个问题。

  1. 您尝试在 when 块中设置一些存根
  2. 您在 then 块中执行您的操作
  3. 您尝试重新定义存根

请参阅 Combining Mocking and Stubbing 了解其工作原理。

def "record count"() {    
        given:
        def now = Instant.now()
        def last10Minutes = now.minus(10,ChronoUnit.MINUTES);
        def recordCount = Spy(QueryDatabase)

        when: "query returning empty flux"
        def result = recordCount.countRecords(last10Minutes,now)

        then:            
        1 * recordCount.query(_) >> Flux.empty()
        result == 0

        when: "query returning the count record"
        def 2ndResult = recordCount.countRecords(last10Minutes,now) == 133

        then:            
        1 * recordCount.query(_) >> Flux.just(CountRecord.builder().value(new BigInteger(133)).build())
        2ndResult == 133    
 }

或者,您可以将其拆分为数据驱动的功能

def "record count"(BigInteger result,Flux input) {    
        given:
        def now = Instant.now()
        def last10Minutes = now.minus(10,ChronoUnit.MINUTES);
        def recordCount = Spy(QueryDatabase)
        recordCount.query(_) >> input

        expect:          
        recordCount.countRecords(last10Minutes,now) == result

        where: 
        result | input
        0      | Flux.empty()
        133    | Flux.just(CountRecord.builder().value(new BigInteger(133)).build())
 }

通常,您会以相反的方式对参数进行排序,但由于通量如此冗长,我觉得这样更易读。

-- 编辑:

我错过了您正在尝试存根您正在测试的同一个对象,这只能使用 partial mocking 来完成,并且通常表明应该重构代码。因此,将 Mock/Stub 替换为 Spy 以进行部分模拟。

,

当您重写 query 方法以返回预期的 Flux 时,这种方式怎么样。在这种情况下,我建议进行 2 次测试:

def "record count when empty Flux"() {
        given:
        def now = Instant.now()
        def last10Minutes = now.minus(10,ChronoUnit.MINUTES);
        def recordCount = new QueryDatabase() {
          @Overriden
          public Flux<CountRecord> query(String query) {
            Flux.empty()
          }
        }

        expect:
        recordCount.countRecords(last10Minutes,now) == 0
 }

def "record count when non empty Flux"() {
        given:
        def now = Instant.now()
        def last10Minutes = now.minus(10,ChronoUnit.MINUTES);
        def recordCount = new QueryDatabase() {
          @Overriden
          public Flux<CountRecord> query(String query) {
            Flux.just(CountRecord.builder().value(new BigInteger(133)).build())
          }
        }

        expect:
        recordCount.countRecords(last10Minutes,now) == 133
 }