20

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.

slashron
  • 277
  • 1
  • 4
  • 13
alexgibbs
  • 2,430
  • 2
  • 16
  • 18
  • 1
    If helps somebody, I'm using GroovyMock in Spock to change static methods in Java code, but this wrongly affected other tests. I used this annotation to fix the problem @ConfineMetaClassChanges – Topera Oct 14 '16 at 00:49
  • See my answer to a similar question https://stackoverflow.com/questions/19493690/using-powermock-with-spock/57848070#57848070 – sweetfa Sep 24 '19 at 04:15

5 Answers5

29

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

Community
  • 1
  • 1
Peter Niederwieser
  • 121,412
  • 21
  • 324
  • 259
6

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

greuze
  • 4,250
  • 5
  • 43
  • 62
slashron
  • 277
  • 1
  • 4
  • 13
3

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.

MichaelZ
  • 1,890
  • 2
  • 13
  • 8
0

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.

Zoose
  • 1
  • 1
  • **You would just pass in the proxy classes to the constructor of the class you're testing.** What does this mean? The static method is not called via instance, so there is no chance to pass the origin/proxy class to on testing class. Any further explanation? – Lebecca Jul 11 '19 at 13:21
  • @Lebecca, Right, but if you create a proxy class and pass it along in the constructor, you can then, in Groovy, mock these proxy class that contain the static methods. For example: ``` ProxyForStaticMethod proxy = Mock(ProxyForStaticMethod); Monster monster = new Monster(proxy); ``` Now, when writing your specs, you can "mock" out the static method by: `proxy.staticMethod() >> 'mocked static'` – Zoose Jul 12 '19 at 21:59
  • The problem here is that the to be tested class is not designed to construct with the proxied class, but with the plain one. I mean, the static class is built as a util for convenience, wrap it makes no sense to me. Your method seems to eliminate direct static method calls in code, right? – Lebecca Jul 15 '19 at 12:16
  • Sorry for the late response. The tested class would have to be modified to include a constructor for testing by passing in the desired proxy classes. The static method does get called by the actual code. However, in the test file you can prevent the actual static call with this proxy and mock it to return whatever. – Zoose Aug 12 '19 at 18:31
0

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

Matteo
  • 183
  • 1
  • 10