alexgibbs
alexgibbs

Reputation: 2480

Mock static method with GroovyMock or similar in Spock

First-timer here, apologies if I've missed anything. I'm hoping to get around a call to a static method using Spock. Feedback would be great

With groovy mocks, I thought I'd be able to get past the static call but haven't found it. For background, I'm in the process of retrofitting tests in legacy java. Refactoring is prohibited. I'm using spock-0.7 with groovy-1.8.

The call to the static method is chained with an instance call in this form:

public class ClassUnderTest{

public void methodUnderTest(Parameter param){
  //everything else commented out
Thing someThing = ClassWithStatic.staticMethodThatReturnsAnInstance().instanceMethod(param);
   }

}

staticMethod returns an instance of ClassWithStatic instanceMethod returns the Thing needed in the rest of the method

If I directly exercise the global mock, it returns the mocked instance ok:

def exerciseTheStaticMock(){
    given:
    def globalMock = GroovyMock(ClassWithStatic,global: true)
    def instanceMock = Mock(ClassWithStatic)

    when:
    println(ClassWithStatic.staticMethodThatReturnsAnInstance().instanceMethod(testParam))

    then:
    interaction{
        1 * ClassWithStatic.staticMethodThatReturnsAnInstance() >> instanceMock
        1 * instanceMock.instanceMethod(_) >> returnThing
    }
}

But if I run the methodUnderTest from the ClassUnderTest:

def failingAttemptToGetPastStatic(){
    given:
    def globalMock = GroovyMock(ClassWithStatic,global: true)
    def instanceMock = Mock(ClassWithStatic)
    ClassUnderTest myClassUnderTest = new ClassUnderTest()

    when:
    myClassUnderTest.methodUnderTest(testParam)

    then:
    interaction{
        1 * ClassWithStatic.staticMethodThatReturnsAnInstance() >> instanceMock
        1 * instanceMock.instanceMethod(_) >> returnThing
    }
}

It throws down a real instance of ClassWithStatic that goes on to fail in its instanceMethod.

Upvotes: 21

Views: 54365

Answers (5)

Matteo
Matteo

Reputation: 327

I have recently found 'spock.mockfree' package, it helps mocking final classes and static classes/methods. It is quite simple as with this framework, in this case, you would need only to Spy() the class under test and @MockStatic the static method you need.

Example:

We used a static method returnA of StaticMethodClass class

public class StaticMethodClass {
    public static String returnA() {
        return "A";
    }
}

here is the calling code

public class CallStaticMethodClass {
    public String useStatic() {
        return StaticMethodClass.returnA();
    }
}

Now we need to test the useStatic method of CallStaticMethodClass class But spock itself does not support mock static methods, and we support

class CallStaticMethodClassTest extends Specification {

    def 'call static method is mocked method'() {
        given:
        CallStaticMethodClass callStaticMethodClass = Spy()
        println("useStatic")
        expect:
        callStaticMethodClass.useStatic() == 'M'
    }

    @MockStatic(StaticMethodClass)
    public static String returnA() {
        return "M";
    }
}

We use the @MockStatic annotation to mark which class needs to be mocked Directly implement the static method that requires mocking under it, the method signature remains the same, but the implementation is different.

Link to the framework: https://github.com/sayweee/spock-mockfree/blob/498e09dc95f841c4061fa8224fcaccfc53904c67/README.md

Upvotes: 0

MichaelZ
MichaelZ

Reputation: 1910

A workaround would be to wrap the static method call into an instance method.

class BeingTested {
    public void methodA() {
        ...

        // was:
        // OtherClass.staticMethod();

        // replaced with:
        wrapperMethod();

        ...
    }

    // add a wrapper method for testing purpose
    void wrapperMethod() {
        OtherClass.staticMethod();
    }
}

Now you can use a Spy to mock out the static method.

class BeingTestedSpec extends Specification {

    @Subject BeingTested object = new BeingTested()

    def "test static method"() {
        given: "a spy into the object"
        def spyObject = Spy(object)

        when: "methodA is called"
        spyObject.methodA()

        then: "the static method wrapper is called"
        1 * spyObject.wrapperMethod() >> {}
    }
}

You can also stub in canned response for the wrapper method if it's supposed to return a value. This solution uses only Spock built-in functions and works with both Java and Groovy classes without any dependencies on PowerMock or GroovyMock.

Upvotes: 3

Zoose
Zoose

Reputation: 1

The way I've gotten around static methods in Groovy/Spock is by creating proxy classes that are substituted out in the actual code. These proxy classes simply return the static method that you need. You would just pass in the proxy classes to the constructor of the class you're testing.

Thus, when you write your tests, you'd reach out to the proxy class (that will then return the static method) and you should be able to test that way.

Upvotes: 0

slashron
slashron

Reputation: 307

Here is how I solved my similar issue (mocking a static method call which is being called from another static class) with Spock (v1.0) and PowerMock (v1.6.4)

import org.junit.Rule
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.rule.PowerMockRule
import spock.lang.Specification
import static org.powermock.api.mockito.PowerMockito.mockStatic
import static org.powermock.api.mockito.PowerMockito.when

@PrepareForTest([YourStaticClass.class])
@PowerMockIgnore(["javax.xml.*", "ch.qos.logback.*", "org.slf4j.*"])
class YourSpockSpec extends Specification {

@Rule
Powermocked powermocked = new Powermocked();

def "something something something something"() {
    mockStatic(YourStaticClass.class)

    when: 'something something'
    def mocked = Mock(YourClass)
    mocked.someMethod(_) >> "return me"

    when(YourStaticClass.someStaticMethod(xyz)).thenReturn(mocked)

    then: 'expect something'
    YourStaticClass.someStaticMethod(xyz).someMethod(abc) == "return me"

   }
}

The @PowerMockIgnore annotation is optional, only use it if there is some conflicts with existing libraries

Upvotes: 7

Peter Niederwieser
Peter Niederwieser

Reputation: 123890

Spock can only mock static methods implemented in Groovy. For mocking static methods implemented in Java, you'll need to use a tool like GroovyMock , PowerMock or JMockit.

PS: Given that these tools pull of some deep tricks in order to achieve their goals, I'd be interested to hear if and how well they work together with tests implemented in Groovy/Spock (rather than Java/JUnit).

Upvotes: 29

Related Questions