简体   繁体   中英

Spock mock calling real method of mocked class

I have Spock integration tests for testing my Java app. For one of the methods, the call is going to the real method 'bMethod()', instead of the stubbed valued being returned. It works fine for another method 'aMethd()', and the stubbed value is returned and the actual method is not called. Both these methods are in same class, Calc. Is there a way to investigate what Spock is doing and why?

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

Try something like

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

Points to note:

  • return value pushed onto stub should be what is returned rather than a closure

  • you have to set the stub calc in math by either a constructor or otherwise

  • the methods you are calling on math take no arguments

Original code with minimal fixes

So first I made m1 and m2 return int instead of void . Then at least you can check the results. Then I also made sure those methods are called correctly without parameters, as Chris already mentioned. So for now the code looks like this:

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

Of course, the tests fail:

Condition not satisfied:

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

Condition not satisfied:

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

The reason for the failure is that you did not inject your mock anywhere, but Math uses a hard-coded Calc instance via Calc calc = new Calc() .

Make tests pass

So we need a way to inject the mock, eg via constructor or setter. You also should change the field type from Calc to CalcIntf because otherwise it is impossible to inject an interface mock. Why create interfaces if you don't use them in your code but code against concrete subclasses?

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

Your described test behaviour

So now the tests pass, but they do not reproduce the behaviour you described:

For one of the methods, the call is going to the real method 'bMethod()', instead of the stubbed valued being returned.

I can imagine one reason why this would happen: The method you want to stub is final , which means that the dynamic proxy implementing the mock cannot override it and thus not stub it. But this is a completely different case from the one you describe here in your code.

See? Your sample code is completely useless if it does not reproduce the problem, which is why in my comment I told you to always provide an MCVE and not some random pseudo code. If somebody reports a bug in your code you also want to know how to reproduce it.

So in order to reproduce a problem like the one you reported, I have to make an educated guess because your code is not helpful. Just change the following in the previous version:

  • int bMethod(int a, int b, int c)final int bMethod(int a, int b, int c)
  • Mock(CalcIntf)Mock(Calc) in 2 places

Now the test result is: one passing and one failing feature method:

Condition not satisfied:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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