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

Stubbed object method value not returned in Spock test

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

的 class
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[=24] 中返回的值=]

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

没有被使用,似乎 recordCount.query(_) >> Flux.empty() 也没有任何影响,它总是 returns 默认的 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. 中执行了您的操作
  4. 您尝试重新定义存根

请参阅 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 方法重写为 return 预期的 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
 }