问题
Im working on a Spring Boot java service that contains a Camel Processor class as follows:
public class MyProc implements Processor {
@Autowired
private LogService logService;
public void process(Exchange e) {
// exchange object processing
logService.update(e)
}
}
And I have the following Spock test:
class MyProcTest extends Specification {
@Shared def logService = Mock(LogService)
@Shared def proc = new MyProc()
def ctx = new DefaultCamelContext()
def exch = new DefaultExchange(ctx)
void setupSpec() {
proc.logService = logService
}
def "log is updated when valid exchange is processed"() {
given:
exch.getIn().setBody(//xml string set on body)
when:
proc.process(exch)
then:
1 * logService.update(_)
}
}
When I run this, I get a failure, stating too few invocations for 1 * logService.update(_) (0 invocations). I tried debugging the code, and in MyProc, the statement is hit, and the logService object when highlighted(in Eclipse) states 'Mock for type LogService named $spock_sharedField_logService', so it looks like the mock has been successfully injected into the MyProc instance.
Im new to Spock and to Groovy so I may be missing something, but shouldn't thetest pass? The mocks method is being invoked when the test is ran, so I don't understand why the test is reporting back that the mocks method has not been called at all. Am I initialising the mock/setting the mock on the MyProc instance/ setting the interactions incorrectly? Is there some Groovy feature or caveat that I've missed? From what I understand, Spock mocks will return a default value from a method when called, which is fine, I just want to check that this particular method has been called as part of proc.process with a valid exchange object
回答1:
A simple answer would be don't use @Shared for mocks/stubs/spies.
@Shared def proc = new MyProcessor()
def test() {
given:
def log = Mock(LogService)
proc.logService = log
when:
proc.process(new Object())
then:
1 * log.update(_)
}
Now the explanation: When you run your spec Spock creates a Specification instance for each test, plus, it creates a shared Specification instance to keep track of shared fields.
Look into org.spockframework.runtime.BaseSpecRunner and you will see two fields:
protected Specification sharedInstance;
protected Specification currentInstance;
Moreover, there are two specification contexts: shared and current. The context keeps the instance, mock context and a mock controller. Here goes the problem. As you declare your mock as shared it is bind to the shared mock controller, but when you declair interactions ('then:' block) Spock adds these interactions to the current mock controller. So when the mock method is invoked (e.g. logService.update(e)) Spock checks if this interaction is allowed with the shared mock controller, as you declare the mock as shared but finds nothing there, because you put the interaction in the current controller.
Don't use mock/stubs/spies as @Shared fields.
来源:https://stackoverflow.com/questions/41148561/spock-mock-verify-returns-0-invocations