【问题标题】:Spock mock calling real method of mocked classSpock模拟调用模拟类的真实方法
【发布时间】:2020-06-08 18:40:21
【问题描述】:

我有 Spock 集成测试来测试我的 Java 应用程序。对于其中一种方法,调用将转到真正的方法“bMethod()”,而不是返回存根值。它适用于另一种方法“aMethd()”,并且返回存根值并且不调用实际方法。这两种方法都在同一个类中,Calc。有没有办法调查 Spock 在做什么以及为什么?

public interface CalcIntf {
  public int aMethod(int a, int b);      
  public int bMethod(int a, int b, int c);
}

MyTestSpec extends Specification {
    def "aMethod test"() {
        given:
          ...
          CalcIntf calcIntf = Mock(CalcIntf)
          calcIntf.aMethod(_,_) >> { return 100; }
        when:
          //service call
        then:
          // asserts go here
    }

    def "bMethod test"() {
        given:
          ...
          CalcIntf calcIntf = Mock(CalcIntf)
          calcIntf.bMethod(_,_,_) >> { return 100; }
        when:
          // service call
        then:
          // asserts go here
    }
}

【问题讨论】:

  • 这里有几件事 - 当存根 aMethod 和 bMethod 时,您需要做 >> 100 而不是返回闭包
  • 另外,m1 和 m2 不带任何参数
  • 数学也有一个硬编码的真实计算,所以没有办法设置存根
  • 嗨 Prakash - 我已将所有 cmets 放入一个连贯的答案中 :-)
  • 关于如何调查 - 通常通过调试。尤其是当真正的代码正在执行时检查你存根的对象是否真的是你的存根,即,当在数学中的断点处时,确保 calc 是 'Mock for CalcIntf called calcIntf'。我想我是你的情况,你会看到它是 Calc 的一个实例。

标签: java spock


【解决方案1】:

经过少量修复的原始代码

所以首先我让m1 和m2 返回int 而不是void。然后至少你可以检查结果。然后我还确保在没有参数的情况下正确调用这些方法,正如 Chris 已经提到的那样。所以现在代码看起来像这样:

package de.scrum_master.stackoverflow.q62269054

import spock.lang.Specification

class MyTestSpec extends Specification {
  def "testing addition using aMethod"() {
    given:
    Math math = new Math()
    CalcIntf calcIntf = Mock(CalcIntf)
    calcIntf.aMethod(_, _) >> { return 123 }
    expect:
    math.m1() == 123
  }

  def "testing addition using bMethod"() {
    given:
    Math math = new Math()
    CalcIntf calcIntf = Mock(CalcIntf)
    calcIntf.bMethod(_, _, _) >> { return 456 }
    expect:
    math.m2() == 456
  }

  interface CalcIntf {
    int aMethod(int a, int b)

    int bMethod(int a, int b, int c)
  }

  static class Calc implements CalcIntf {
    int aMethod(int a, int b) {
      return a + b
    }

    int bMethod(int a, int b, int c) {
      return a + b + c
    }
  }

  static class Math {
    Calc calc = new Calc()

    int m1() {
      calc.aMethod(1, 2)
    }

    int m2() {
      calc.bMethod(1, 2, 3)
    }
  }
}

当然,测试失败了:

Condition not satisfied:

math.m1() == 123
|    |    |
|    3    false
...

Condition not satisfied:

math.m2() == 456
|    |    |
|    6    false
...

失败的原因是你没有在任何地方注入你的模拟,但是Math通过Calc calc = new Calc()使用了一个硬编码的Calc实例。

使测试通过

所以我们需要一种注入模拟的方法,例如通过构造函数或设置器。您还应该将字段类型从Calc 更改为CalcIntf,否则无法注入接口模拟。如果您不在代码中使用接口而是针对具体子类编写代码,为什么还要创建接口?

package de.scrum_master.stackoverflow.q62269054

import spock.lang.Specification

class MyTestSpec extends Specification {
  def "testing addition using aMethod"() {
    given:
    CalcIntf calcIntf = Mock(CalcIntf)
    calcIntf.aMethod(_, _) >> { return 123 }
    Math math = new Math(calcIntf)
    expect:
    math.m1() == 123
  }

  def "testing addition using bMethod"() {
    given:
    CalcIntf calcIntf = Mock(CalcIntf)
    calcIntf.bMethod(_, _, _) >> { return 456 }
    Math math = new Math(calcIntf)
    expect:
    math.m2() == 456
  }

  interface CalcIntf {
    int aMethod(int a, int b)

    int bMethod(int a, int b, int c)
  }

  static class Calc implements CalcIntf {
    int aMethod(int a, int b) {
      return a + b
    }

    int bMethod(int a, int b, int c) {
      return a + b + c
    }
  }

  static class Math {
    CalcIntf calcIntf

    Math(CalcIntf calcIntf) {
      this.calcIntf = calcIntf
    }

    int m1() {
      calcIntf.aMethod(1, 2)
    }

    int m2() {
      calcIntf.bMethod(1, 2, 3)
    }
  }
}

您描述的测试行为

所以现在测试通过了,但它们没有重现您描述的行为:

对于其中一种方法,调用将转到真正的方法“bMethod()”,而不是返回的存根值。

我可以想象会发生这种情况的一个原因:您要存根的方法是final,这意味着实现模拟的动态代理无法覆盖它,因此不会存根它。但这与您在代码中描述的情况完全不同。

看到了吗?如果您的示例代码不能重现问题,那么它就完全没有用,这就是为什么在我的评论中我告诉您始终提供MCVE 而不是一些随机的伪代码。如果有人报告了您的代码中的错误,您也想知道如何重现它。

因此,为了重现您报告的问题,我必须做出有根据的猜测,因为您的代码没有帮助。只需在之前的版本中更改以下内容:

  • int bMethod(int a, int b, int c) → final int bMethod(int a, int b, int c)
  • Mock(CalcIntf) → Mock(Calc) 在两个地方

现在测试结果是:一种通过,一种失败的特征方法:

Condition not satisfied:

math.m2() == 456
|    |    |
|    6    false
...

【讨论】:

  • 示例代码从来都不是一个完整的可编译和可运行的代码。为混乱道歉。然而,唯一重要的一点是,在同一个接口和实现类上的两种方法中,一种会被嘲笑,而另一种不会。不涉及final关键字。除了参数列表和异常列表之外,这两种方法没有区别,当然方法名称也不同。我还没有能够深入挖掘。可能我今天会试试。谢谢。
  • 如果它从来就不是可运行的,它的意义是什么?这里显然缺少信息。了解如何将您的真实代码浓缩和抽象成小东西,在不泄露公司信息的情况下仍然重现问题。这是基本的开发人员技能。没有人可以调试伪代码——你可以吗?如果是这样,请教我。如果您不知道自己问题的解决方案,请不要为他人决定什么是重要的。
【解决方案2】:

试试类似的东西

given: 'a math with a calculator that always returns 100'
    def calc = Stub(CalcIntf) {
        bMethod(_,_,_) >> 100
    }
    def math = new Math(calc)

when: 'the math is asked to calculate something'
    def result = math.m2()

then: 'it returns 100'
    result == 100

注意事项:

  • 推送到存根上的返回值应该是返回值而不是闭包

  • 您必须通过构造函数或其他方式在数学中设置存根计算

  • 您在数学上调用的方法不带参数

【讨论】:

  • 我已经简化了代码。所以,奇怪的事情是一个方法被正确替换,而另一种方法却不是,它们都在同一个类和接口中。另外,需要注意的是,方法签名之间的唯一区别是方法名称和参数列表。我可以看到在这两种情况下都创建了 cglib 代理。
  • 嗨 Prakash,老实说,通过编辑您问题中的代码,这意味着我在答案中提到的大部分内容不再有意义,而且您可能已经隐藏了问题的原因。看起来确实很奇怪,我打算明天在我手头有笔记本电脑时运行代码,但现在看不到代码。如果你能再次分享完整的代码,我会看看(英国时间)
  • 嗨,克里斯,很抱歉。但是,它不是实际的代码,它只是一个表示。关于不返回闭包,我看到问题的实际方法返回类似于 List 的内容,并且存根适用于一种方法,但适用于另一种方法。它让我头晕目眩。
  • 啊,如果你能分享完整的代码,那会很方便。在原始帖子中,据我所知,任何一个测试都无法调用存根,因为 Math 正在创建(真实)Calc 的实例,并且没有任何东西将其更改为存根。因此,问题很可能在当前共享的代码中不可见 - 我猜您已经共享了您认为必须包含问题的代码,但这将是一个很大的假设。
  • 无法共享实际代码。我将深入研究代理/aop,看看我是否能弄清楚。
猜你喜欢
  • 2019-08-29
  • 1970-01-01
  • 2016-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多