code:
public class A{
public void method(User user){
String name = user.getName();
if("Tom".equals(name)){
method1(user);
}else{
method2(user);
}
}
}
I want to write a test about A#method() by using spock. As you can see,When I test method(),I don't care the execution of method1() or method2(),I just want to verify that those two methods will invoked in certain case. My test code:
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
class ASpec extends Specification {
def "method"(){
given:
def process = new A()
expect:
process.method(input as User)
where:
input || _
new User(name:"Tom") || _
}
}
I find that method1() does executed,but I think that the meaning of a unit test is just to verify the process is right, so I don't want method1() executed actually,but just invoked. What should I do?
p.s. I also use spy:
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
class ASpec extends Specification {
def "method"(){
given:
A process = Spy()
expect:
process.method(input as User)
where:
input || _
new User(name:"Tom") || _
}
}
but the method1() also executed.
Leonard is totally right: In >99% of normal unit tests
What you want is technically possible, but it usually is a code smell, if it is necessary. For argument's sake, let us assume your class under test looks like this:
class A {
void method(User user) {
if ('Tom' == user.name)
method1(user)
else
method2(user)
}
void method1(User user) {
println "method1, doing something expensive & slow with $user.name"
}
void method2(User user) {
println "method2, doing something expensive & slow with $user.name"
}
}
class User {
String name
}
Now you can test like this:
import spock.lang.Specification
import spock.lang.Unroll
class ATest extends Specification {
@Unroll('user #user.name')
def 'expensive calculation (no mocks)'() {
given:
def process = new A()
expect:
process.method(user)
where:
user << [new User(name: 'Tom'), new User(name: 'Tina')]
}
@Unroll('user #user.name')
def 'expensive calculation (spy)'() {
given:
A process = Spy()
when:
process.method(user)
then:
1 * process."$calledMethod"(user) >> null
where:
user | calledMethod
new User(name: 'Tom') | 'method1'
new User(name: 'Tina') | 'method2'
}
While in the first test, you will see the log output for the called methods, for the second one you will not, because the two methods have been stubbed out. By default, a spy calls the original method, if you do not change its behaviour.
Leonard also said that you should refactor, if the helper methods do something that is not the core responsibility of the class under test. Let us assume, we have this case here. Then you can refactor and test like this:
class B {
ExpensiveCalculation calculation
void method(User user) {
if ('Tom' == user.name)
this.calculation.method1(user)
else
this.calculation.method2(user)
}
}
class ExpensiveCalculation {
void method1(User user) {
println "method1, doing something expensive & slow with $user.name"
}
void method2(User user) {
println "method2, doing something expensive & slow with $user.name"
}
}
@Unroll('user #user.name')
def 'expensive calculation (factored out, mocked)'() {
given:
def calculation = Mock(ExpensiveCalculation)
def process = new B(calculation: calculation)
when:
process.method(user)
then:
1 * calculation."$calledMethod"(user)
where:
user | calledMethod
new User(name: 'Tom') | 'method1'
new User(name: 'Tina') | 'method2'
}
See how easy it is to inject a mock and verify its behaviour? No more ugly self-spying on the class under test, but clear separation of concerns. But before you use interaction testing (verifying behaviour), think about whether you need it at all. Just because it is possible, it does not mean it is always necessary.
Try it in the Groovy Web Console and click on the "Result" tab to see the test report.