Don't understand why my Mocked method always returns null

8,675 views
Skip to first unread message

alpheratz

unread,
Oct 13, 2011, 9:12:33 AM10/13/11
to Spock Framework - User
Given this Class Under Test:

---
package macau.gr8

class Grader2 {
def expectedAnswers
def graderFileReader

def grade(String s) {
println "grade: $s, $graderFileReader, $expectedAnswers"
def candidateAnswers =
graderFileReader.readGradesListFromFile(s)
println candidateAnswers

grade(candidateAnswers)
}

def grade(List candidateAnswers = []) {
if (expectedAnswers?.size() != candidateAnswers?.size())
0.0
else {
def count = 0
expectedAnswers.eachWithIndex {o,index ->
if (o == candidateAnswers[index]) count ++
}

count / expectedAnswers.size()
}
}
}
---

And this helper to be mocked:

---
class GraderFileReader {
def readGradesListFromFile(name) {
def txt = new File(name).text
txt.split(',') as List
}
}
---

And this test class:

---
class GraderTest3 extends Specification {

def "test"() {
setup:
def grader = new Grader2(expectedAnswers: ['a','b','c'])
def graderFileReader = Mock(GraderFileReader)
1 * graderFileReader.readGradesListFromFile(_) >>
['a','b','c']
grader.graderFileReader = graderFileReader

when:
def res = grader.grade('100pct.txt')

then:
res == 1.0D
1 * graderFileReader.readGradesListFromFile('100pct.txt')
}
}
---

Why does 'graderFileReader.readGradesListFromFile(s)' always return
null in my testing?

What am I doing wrong?

I always get:

---
grade: 100pct.txt, Mock for type 'GraderFileReader' named
'graderFileReader', [a, b, c]
null

Condition not satisfied:

res == 1.0D
| |
0.0 false
<Click to see difference>

at GraderTest3.test(GraderTest3.groovy:27)
---

I have hamcrest-core-1.2.jar,spock-core-0.5-groovy-1.7.jar, cgilib-
nodep-2.2.jar in my classpath. Using groovy 1.7.10.

Thoughts/suggestions gratefully recieved.

BOB

Peter Niederwieser

unread,
Oct 13, 2011, 12:33:46 PM10/13/11
to spockfr...@googlegroups.com
Interactions in then-blocks have precedence over other interactions. Since you don't specify a return value for the former, null is returned.

If you mock and stub the same call, you have to do it in one interaction; you can't (and don't have to!) split up mocking and stubbing into two interactions. Same as EasyMock, JMock, etc., but different compared to Mockito.

Cheers,
Peter

> --
> You received this message because you are subscribed to the Google Groups "Spock Framework - User" group.
> To post to this group, send email to spockfr...@googlegroups.com.
> To unsubscribe from this group, send email to spockframewor...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/spockframework?hl=en.
>

alpheratz

unread,
Oct 14, 2011, 3:27:53 AM10/14/11
to Spock Framework - User
OK. Thanks.

So this works:

---
def "test"() {
setup:
def grader = new Grader2(expectedAnswers: ['a','b','c'])
def graderFileReader = Mock(GraderFileReader)
//1 * graderFileReader.readGradesListFromFile(_) >>
['a','b','c']
grader.graderFileReader = graderFileReader

when:
def res = grader.grade('100pct.txt')

then:
res == 1.0D
1 * graderFileReader.readGradesListFromFile(_) >>
['a','b','c']
}
---

It seems 'backwards' to me, still.

It appears that the then: section is establishing the behaviour for
readGradesListFromFile before execution begins as well as providing
the assertion check at the end of execution.

Is this correct?

If so, when would it be even _possible_ to have an interaction in the
setup part...

Any flow-of-control I postulate seems odd: 'res ==' MUST come
cronologically after the call to 'grade()' but the '1 *
graderFileReader.readGradesListFromFile' stuff MUST be in place before
the call to 'grade()'

hmmm?

And why is my situation apparently quite different to Ken Kousen's
"The right way to mock a Klingon" at
http://kousenit.wordpress.com/2011/08/20/i-think-i-get-spock-mocks-now/
or the emailService example at
http://www.intelligrape.com/blog/2011/04/07/interaction-based-testing-using-spock-in-grails/?

Confused/feeling dumb!

Loving what I am 'getting' of Spock but this feels 'odd' to me still.

Cheers,

BOB

Peter Niederwieser

unread,
Oct 15, 2011, 2:46:34 AM10/15/11
to spockfr...@googlegroups.com
I've explained it all in my comments to Ken's blog post, but I'll try one more time:

On 14.10.2011, at 09:27, alpheratz wrote:

> OK. Thanks.
>
> So this works:
>
> ---
> def "test"() {
> setup:
> def grader = new Grader2(expectedAnswers: ['a','b','c'])
> def graderFileReader = Mock(GraderFileReader)
> //1 * graderFileReader.readGradesListFromFile(_) >>
> ['a','b','c']
> grader.graderFileReader = graderFileReader
>
> when:
> def res = grader.grade('100pct.txt')
>
> then:
> res == 1.0D
> 1 * graderFileReader.readGradesListFromFile(_) >>
> ['a','b','c']
> }
> ---
>
> It seems 'backwards' to me, still.
>
> It appears that the then: section is establishing the behaviour for
> readGradesListFromFile before execution begins as well as providing
> the assertion check at the end of execution.
>
> Is this correct?

Well, "1 * foo.bar()" isn't an assertion, at least not in the classical sense. It just _declares_ an expected interaction. The verification will happen at other points.

> If so, when would it be even _possible_ to have an interaction in the
> setup part...

Technically, an interaction _must_ be declared in the setup-block. This is how most mocking frameworks work (except for Mockito). Spock additionally allows an interaction to be declared in the then-block, because this is a more natural way to think about it. (After all, the interaction occurs after the stimulus in the when-block.) However, what happens internally is that this declaration gets moved to the setup-block, because the mocking framework needs to have all information up front.

> Any flow-of-control I postulate seems odd: 'res ==' MUST come
> cronologically after the call to 'grade()' but the '1 *
> graderFileReader.readGradesListFromFile' stuff MUST be in place before
> the call to 'grade()'

It would be more idiomatic to reorder the statements in the then-block so that interactions come before assertions. This is a more natural way to think about it. But Spock doesn't care; it will move all interactions from the then-block to the setup-block. To be more precise, it moves them to right before the corresponding when-block. If you find this confusing, just declare everything in the setup-block. This is what people are used to from EasyMock/JMock.

> And why is my situation apparently quite different to Ken Kousen's
> "The right way to mock a Klingon" at
> http://kousenit.wordpress.com/2011/08/20/i-think-i-get-spock-mocks-now/
> or the emailService example at
> http://www.intelligrape.com/blog/2011/04/07/interaction-based-testing-using-spock-in-grails/?

Not sure what you mean by that.

Cheers,
Peter

alpheratz

unread,
Oct 15, 2011, 7:58:11 AM10/15/11
to Spock Framework - User
Thanks for having the patience to explain to me, Peter! Much
appreciated.

---
Technically, an interaction _must_ be declared in the setup-block.
This is how most mocking frameworks work (except for Mockito). Spock
additionally allows an interaction to be declared in the then-block,
because this is a more natural way to think about it. (After all, the
interaction occurs after the stimulus in the when-block.) However,
what happens internally is that this declaration gets moved to the
setup-block, because the mocking framework needs to have all
information up front.
---

Thanks for explaining the movement of stuff...I assumed that you were
doing some hoopy stuff :-)

I didn't find this "a more natural way" I WAS probably thinking that I
was setting up a predicate/constraint...and so having everything in
the setup made sense...

So I now have:

---
def "Given a mock file2"() {
setup: "Establish the grader and a mocked GraderFileReader"
def graderFileReader = Mock(GraderFileReader)
1 * graderFileReader.readGradesListFromFile(_) >>
['a','b','c']
//0 * _

grader.graderFileReader = graderFileReader

when: "Read a paper's answers from a given file"
def res = grader.grade('100pct.txt')

then: "Ensure expected behaviour"
res == 1.0D
}
---

This is fine, thanks.

Note that if I uncomment the '0 * _' bit I get an error:

---
"C:\Program Files\Java\jdk1.7.0\bin\java" -Didea.launcher.port=7534 "-
Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA
110.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files
(x86)\JetBrains\IntelliJ IDEA 110.3\lib\idea_rt.jar;C:\Program Files
(x86)\JetBrains\IntelliJ IDEA 110.3\plugins\junit\lib\junit-rt.jar;C:
\Program Files\Java\jdk1.7.0\jre\lib\alt-rt.jar;C:\Program Files\Java
\jdk1.7.0\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.7.0\jre\lib
\deploy.jar;C:\Program Files\Java\jdk1.7.0\jre\lib\javaws.jar;C:
\Program Files\Java\jdk1.7.0\jre\lib\jce.jar;C:\Program Files\Java
\jdk1.7.0\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.7.0\jre\lib
\management-agent.jar;C:\Program Files\Java\jdk1.7.0\jre\lib
\plugin.jar;C:\Program Files\Java\jdk1.7.0\jre\lib\resources.jar;C:
\Program Files\Java\jdk1.7.0\jre\lib\rt.jar;C:\Program Files\Java
\jdk1.7.0\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.7.0\jre\lib
\ext\localedata.jar;C:\Program Files\Java\jdk1.7.0\jre\lib\ext
\sunec.jar;C:\Program Files\Java\jdk1.7.0\jre\lib\ext
\sunjce_provider.jar;C:\Program Files\Java\jdk1.7.0\jre\lib\ext
\zipfs.jar;C:\DEVELOPMENT\Grader\out\test\Grader;C:\DEVELOPMENT\Grader
\out\production\Grader;C:\Users\Bob\.gradle\cache\org.codehaus.groovy
\groovy-all\jars\groovy-all-1.7.10.jar;C:\Users\Bob\.gradle\cache\cglib
\cglib-nodep\jars\cglib-nodep-2.2.jar;C:\Users\Bob\.gradle\cache
\org.objenesis\objenesis\jars\objenesis-1.2.jar;C:\Users\Bob\.gradle
\cache\org.spockframework\spock-core\jars\spock-core-0.5-
groovy-1.7.jar;C:\Users\Bob\.gradle\cache\org.hamcrest\hamcrest-core
\jars\hamcrest-core-1.2.jar;C:\Users\Bob\.gradle\cache\junit\junit-dep
\jars\junit-dep-4.8.2.jar"
com.intellij.rt.execution.application.AppMain
com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4
macau.gr8.GraderTest3

groovy.lang.MissingMethodException: No signature of method:
java.lang.Integer.multiply() is applicable for argument types:
(spock.lang.Specification$Wildcard) values: [_]
Possible solutions: multiply(java.lang.Character),
multiply(java.lang.Number)
at macau.gr8.GraderTest3.Given a mock file2(GraderTest3.groovy:31)


Process finished with exit code -1
---

If I use the pre-0.4 version '0 * _._" all is fine.

Thanks.

BOB
Reply all
Reply to author
Forward
0 new messages