Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PowerMockito can mock static JRE methods in JUnit but not in Spock #1014

Open
kriegaex opened this issue Aug 24, 2019 · 1 comment

Comments

@kriegaex
Copy link

commented Aug 24, 2019

I sometimes help people on StackOverflow or development teams I am coaching as an Agile Coach with test automation. Usually I introduce them to Spock + Geb and tell them to take their fingers off of PowerMock and similar frameworks. Instead I am telling them to refactor for better testability.

Anyway, I do know how to use PowerMockito from Spock and for the most part it works nicely, but in the past and then again lately I ran into problems when trying to do something like the following JUnit test in Spock:

package de.scrum_master.test;

import java.sql.Date;
import java.util.UUID;

public class ClassCallingStaticJREMethods {
  public String genUUID() {
    return UUID.randomUUID().toString();
  }

  public Date genDate(String date) {
    return Date.valueOf(date);
  }
}
package de.scrum_master.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.sql.Date;
import java.util.UUID;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.anyString;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ ClassCallingStaticJREMethods.class })
public class JUnitTest {
  @Test
  public void testUUID() {
    final String id = "493410b3-dd0b-4b78-97bf-289f50f6e74f";
    UUID uuid = UUID.fromString(id);
    System.out.println(uuid);

    // Static mocking works for system (JDK, JRE) classes if the class *using* the
    // system class is present in @PrepareForTest instead of the system class itself
    mockStatic(UUID.class);
    when(UUID.randomUUID()).thenReturn(uuid);

    ClassCallingStaticJREMethods underTest = new ClassCallingStaticJREMethods();
    assertEquals(id, underTest.genUUID());
  }

  @Test
  public void testSQLDate() {
    Date date = new Date(1971 - 1900, 5 - 1, 8);
    System.out.println(date);

    // Static mocking works for system (JDK, JRE) classes if the class *using* the
    // system class is present in @PrepareForTest instead of the system class itself
    mockStatic(Date.class);
    when(Date.valueOf(anyString())).thenReturn(date);

    ClassCallingStaticJREMethods underTest = new ClassCallingStaticJREMethods();
    assertEquals(date, underTest.genDate("1998-12-20"));
  }
}

So far, so good. Now let's convert that test into a Spock test:

package de.scrum_master.test

import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.spockframework.runtime.Sputnik
import spock.lang.Specification

import java.sql.Date

import static org.mockito.ArgumentMatchers.anyString
import static org.powermock.api.mockito.PowerMockito.mockStatic
import static org.powermock.api.mockito.PowerMockito.when

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
@PrepareForTest([ClassCallingStaticJREMethods.class])
class SpockTest extends Specification {
  def testUUID() {
    given:
    final String id = "493410b3-dd0b-4b78-97bf-289f50f6e74f";
    UUID uuid = UUID.fromString(id)
    println uuid
    mockStatic(UUID)
    // MissingMethodInvocationException:
    // when() requires an argument which has to be 'a method call on a mock'
    when(UUID.randomUUID()).thenReturn(uuid)
    ClassCallingStaticJREMethods underTest = new ClassCallingStaticJREMethods()

    when:
    String ret = underTest.genUUID()

    then:
    id == ret
  }

  def testSQLDate() {
    given:
    def date = new Date(1971 - 1900, 5 - 1, 8)
    println date
    mockStatic(Date)
    // IllegalArgumentException at java.sql.Date.valueOf(Date.java:143)
    // because the Date constructor is actually being called
    when(Date.valueOf(anyString())).thenReturn(date)
    ClassCallingStaticJREMethods underTest = new ClassCallingStaticJREMethods()

    when:
    def ret = underTest.genDate("1998-12-20" as String)

    then:
    ret == date
  }
}

If you want a repeatable build, put the class under test into src/main/java and the tests under src/test/java and src/test/groovy, respectively. This is the standard Maven project layout. Then build using this POM (I condensed a POM from bigger project into this one):

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>de.scrum-master</groupId>
  <artifactId>test</artifactId>
  <version>1.0-SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>2.23.0</version>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-core</artifactId>
      <version>2.0.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-api-mockito2</artifactId>
      <version>2.0.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-junit4</artifactId>
      <version>2.0.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy-all</artifactId>
      <version>2.5.7</version>
      <type>pom</type>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <artifactId>groovy-test-junit5</artifactId>
          <groupId>org.codehaus.groovy</groupId>
        </exclusion>
        <exclusion>
          <artifactId>groovy-testng</artifactId>
          <groupId>org.codehaus.groovy</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy</artifactId>
      <version>2.5.7</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.spockframework</groupId>
      <artifactId>spock-core</artifactId>
      <version>1.3-groovy-2.5</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib-nodep</artifactId>
      <version>3.2.5</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.objenesis</groupId>
      <artifactId>objenesis</artifactId>
      <version>2.5.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <executions>
          <execution>
            <id>default-compile</id>
            <phase>compile</phase>
            <goals>
              <goal>compile</goal>
            </goals>
            <configuration>
              <source>1.8</source>
              <target>1.8</target>
              <useIncrementalCompilation>false</useIncrementalCompilation>
              <encoding>UTF-8</encoding>
              <compilerId>groovy-eclipse-compiler</compilerId>
              <fork>true</fork>
            </configuration>
          </execution>
          <execution>
            <id>default-testCompile</id>
            <phase>test-compile</phase>
            <goals>
              <goal>testCompile</goal>
            </goals>
            <configuration>
              <source>1.8</source>
              <target>1.8</target>
              <useIncrementalCompilation>false</useIncrementalCompilation>
              <encoding>UTF-8</encoding>
              <compilerId>groovy-eclipse-compiler</compilerId>
              <fork>true</fork>
            </configuration>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-eclipse-compiler</artifactId>
            <version>3.4.0-01</version>
            <scope>compile</scope>
          </dependency>
          <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-eclipse-batch</artifactId>
            <version>2.5.7-01</version>
            <scope>compile</scope>
          </dependency>
        </dependencies>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <useIncrementalCompilation>false</useIncrementalCompilation>
          <encoding>UTF-8</encoding>
          <compilerId>groovy-eclipse-compiler</compilerId>
          <fork>true</fork>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.codehaus.groovy</groupId>
        <artifactId>groovy-eclipse-compiler</artifactId>
        <version>3.4.0-01</version>
        <extensions>true</extensions>
        <executions>
          <execution>
            <id>default-add-groovy-build-paths</id>
            <phase>initialize</phase>
            <goals>
              <goal>add-groovy-build-paths</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.18.1</version>
        <executions>
          <execution>
            <id>default-test</id>
            <phase>test</phase>
            <goals>
              <goal>test</goal>
            </goals>
            <configuration>
              <excludes>
                <exclude>**/IT*.java</exclude>
              </excludes>
              <argLine>-Dfile.encoding=UTF8</argLine>
            </configuration>
          </execution>
        </executions>
        <configuration>
          <excludes>
            <exclude>**/IT*.java</exclude>
          </excludes>
          <argLine>-Dfile.encoding=UTF8</argLine>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

The Maven build mvn clean test > build.log 2>&1 says:

[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< de.scrum-master:test >------------------------
[INFO] Building test 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ test ---
[INFO] Deleting ~\Documents\java-src\Spock_ML_PowerMockStaticJREMethods\target
[INFO] 
[INFO] --- groovy-eclipse-compiler:3.4.0-01:add-groovy-build-paths (default-add-groovy-build-paths) @ test ---
[INFO] Adding /src/main/groovy to the list of source folders
[INFO] Adding /src/test/groovy to the list of test source folders
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ test ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory ~\Documents\java-src\Spock_ML_PowerMockStaticJREMethods\src\main\resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ test ---
[INFO] Using Groovy-Eclipse compiler to compile both Java and Groovy files
[INFO] Compiling in a forked process using ~\.m2\repository\org\codehaus\groovy\groovy-eclipse-batch\2.5.7-01\groovy-eclipse-batch-2.5.7-01.jar
[INFO] Aktive Codepage: 65001.
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ test ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory ~\Documents\java-src\Spock_ML_PowerMockStaticJREMethods\src\test\resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ test ---
[INFO] Using Groovy-Eclipse compiler to compile both Java and Groovy files
[INFO] Compiling in a forked process using ~\.m2\repository\org\codehaus\groovy\groovy-eclipse-batch\2.5.7-01\groovy-eclipse-batch-2.5.7-01.jar
[INFO] Aktive Codepage: 65001.
[INFO] 
[INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ test ---
[INFO] Surefire report directory: ~\Documents\java-src\Spock_ML_PowerMockStaticJREMethods\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Aktive Codepage: 65001.
Running de.scrum_master.test.JUnitTest
493410b3-dd0b-4b78-97bf-289f50f6e74f
1971-05-08
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.247 sec - in de.scrum_master.test.JUnitTest
Running de.scrum_master.test.SpockTest
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
Notifications are not supported when all test-instances are created first!
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
493410b3-dd0b-4b78-97bf-289f50f6e74f
Notifications are not supported when all test-instances are created first!
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
Notifications are not supported for behaviour ALL_TESTINSTANCES_ARE_CREATED_FIRST
1971-05-08
Tests run: 3, Failures: 0, Errors: 3, Skipped: 0, Time elapsed: 2.018 sec <<< FAILURE! - in de.scrum_master.test.SpockTest
testUUID(de.scrum_master.test.SpockTest)  Time elapsed: 1.023 sec  <<< ERROR!
org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

	at de.scrum_master.test.SpockTest.testUUID(SpockTest.groovy:28)

testSQLDate(de.scrum_master.test.SpockTest)  Time elapsed: 0.119 sec  <<< ERROR!
java.lang.IllegalArgumentException: null
	at java.sql.Date.valueOf(Date.java:143)
	at de.scrum_master.test.SpockTest.testSQLDate(SpockTest.groovy:45)

Test mechanism  Time elapsed: 0.121 sec  <<< ERROR!
org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Misplaced or misused argument matcher detected here:

-> at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:55)

You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
    when(mock.get(anyInt())).thenReturn(null);
    doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
    verify(mock).someMethod(contains("foo"))

This message may appear after an NullPointerException if the last matcher is returning an object 
like any() but the stubbed method signature expect a primitive argument, in this case,
use primitive alternatives.
    when(mock.get(any())); // bad use, will raise NPE
    when(mock.get(anyInt())); // correct usage use

Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.

	at org.junit.runner.notification.SynchronizedRunListener.testFinished(SynchronizedRunListener.java:56)
	at org.junit.runner.notification.RunNotifier$7.notifyListener(RunNotifier.java:190)
	at org.junit.runner.notification.RunNotifier$SafeNotifier.run(RunNotifier.java:72)
	at org.junit.runner.notification.RunNotifier.fireTestFinished(RunNotifier.java:187)
	at org.spockframework.runtime.JUnitSupervisor.afterFeature(JUnitSupervisor.java:197)
	at org.spockframework.runtime.BaseSpecRunner.runFeature(BaseSpecRunner.java:236)
	at org.spockframework.runtime.BaseSpecRunner.runFeatures(BaseSpecRunner.java:185)
	at org.spockframework.runtime.BaseSpecRunner.doRunSpec(BaseSpecRunner.java:95)
	at org.spockframework.runtime.BaseSpecRunner$1.invoke(BaseSpecRunner.java:81)
	at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:484)
	at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:467)
	at org.spockframework.runtime.BaseSpecRunner.runSpec(BaseSpecRunner.java:73)
	at org.spockframework.runtime.BaseSpecRunner.run(BaseSpecRunner.java:64)
	at org.spockframework.runtime.Sputnik.run(Sputnik.java:63)
	at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:283)
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:173)
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
	at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:128)
	at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)


Results :

Tests in error: 
  
Misplaced or misused argument matcher detected here:

-> at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:55)

You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
    when(mock.get(anyInt())).thenReturn(null);
    doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
    verify(mock).someMethod(contains("foo"))

This message may appear after an NullPointerException if the last matcher is returning an object 
like any() but the stubbed method signature expect a primitive argument, in this case,
use primitive alternatives.
    when(mock.get(any())); // bad use, will raise NPE
    when(mock.get(anyInt())); // correct usage use

Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.

  SpockTest.testSQLDate:45 » IllegalArgument
  SpockTest.testUUID:28 MissingMethodInvocation 
when() requires an argument whi...

Tests run: 5, Failures: 0, Errors: 3, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.944 s
[INFO] Finished at: 2019-08-24T12:31:34+07:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.18.1:test (default-test) on project test: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.18.1:test failed: There was an error in the forked process
[ERROR] org.apache.maven.surefire.testset.TestSetFailedException: org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
[ERROR] Misplaced or misused argument matcher detected here:
[ERROR] 
[ERROR] -> at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:55)
[ERROR] 
[ERROR] You cannot use argument matchers outside of verification or stubbing.
[ERROR] Examples of correct usage of argument matchers:
[ERROR]     when(mock.get(anyInt())).thenReturn(null);
[ERROR]     doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
[ERROR]     verify(mock).someMethod(contains("foo"))
[ERROR] 
[ERROR] This message may appear after an NullPointerException if the last matcher is returning an object 
[ERROR] like any() but the stubbed method signature expect a primitive argument, in this case,
[ERROR] use primitive alternatives.
[ERROR]     when(mock.get(any())); // bad use, will raise NPE
[ERROR]     when(mock.get(anyInt())); // correct usage use
[ERROR] 
[ERROR] Also, this error might show up because you use argument matchers with methods that cannot be mocked.
[ERROR] Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
[ERROR] Mocking methods declared on non-public parent classes is not supported.
[ERROR] 
[ERROR] 	at org.apache.maven.surefire.common.junit4.JUnit4RunListener.rethrowAnyTestMechanismFailures(JUnit4RunListener.java:213)
[ERROR] 	at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:133)
[ERROR] 	at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
[ERROR] 	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
[ERROR] 	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)
[ERROR] Caused by: org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
[ERROR] Misplaced or misused argument matcher detected here:
[ERROR] 
[ERROR] -> at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:55)
[ERROR] 
[ERROR] You cannot use argument matchers outside of verification or stubbing.
[ERROR] Examples of correct usage of argument matchers:
[ERROR]     when(mock.get(anyInt())).thenReturn(null);
[ERROR]     doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
[ERROR]     verify(mock).someMethod(contains("foo"))
[ERROR] 
[ERROR] This message may appear after an NullPointerException if the last matcher is returning an object 
[ERROR] like any() but the stubbed method signature expect a primitive argument, in this case,
[ERROR] use primitive alternatives.
[ERROR]     when(mock.get(any())); // bad use, will raise NPE
[ERROR]     when(mock.get(anyInt())); // correct usage use
[ERROR] 
[ERROR] Also, this error might show up because you use argument matchers with methods that cannot be mocked.
[ERROR] Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
[ERROR] Mocking methods declared on non-public parent classes is not supported.
[ERROR] 
[ERROR] 	at org.junit.runner.notification.SynchronizedRunListener.testFinished(SynchronizedRunListener.java:56)
[ERROR] 	at org.junit.runner.notification.RunNotifier$7.notifyListener(RunNotifier.java:190)
[ERROR] 	at org.junit.runner.notification.RunNotifier$SafeNotifier.run(RunNotifier.java:72)
[ERROR] 	at org.junit.runner.notification.RunNotifier.fireTestFinished(RunNotifier.java:187)
[ERROR] 	at org.spockframework.runtime.JUnitSupervisor.afterFeature(JUnitSupervisor.java:197)
[ERROR] 	at org.spockframework.runtime.BaseSpecRunner.runFeature(BaseSpecRunner.java:236)
[ERROR] 	at org.spockframework.runtime.BaseSpecRunner.runFeatures(BaseSpecRunner.java:185)
[ERROR] 	at org.spockframework.runtime.BaseSpecRunner.doRunSpec(BaseSpecRunner.java:95)
[ERROR] 	at org.spockframework.runtime.BaseSpecRunner$1.invoke(BaseSpecRunner.java:81)
[ERROR] 	at org.spockframework.runtime.BaseSpecRunner.invokeRaw(BaseSpecRunner.java:484)
[ERROR] 	at org.spockframework.runtime.BaseSpecRunner.invoke(BaseSpecRunner.java:467)
[ERROR] 	at org.spockframework.runtime.BaseSpecRunner.runSpec(BaseSpecRunner.java:73)
[ERROR] 	at org.spockframework.runtime.BaseSpecRunner.run(BaseSpecRunner.java:64)
[ERROR] 	at org.spockframework.runtime.Sputnik.run(Sputnik.java:63)
[ERROR] 	at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:283)
[ERROR] 	at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:173)
[ERROR] 	at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
[ERROR] 	at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:128)
[ERROR] 	... 3 more
[ERROR] 
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/PluginExecutionException

Here is an archive with all source code files quoted above incl. Maven POM:
Spock_PowerMockStaticJREMethods.zip

My actual question is not primarily a request to the Spock maintainers to make Spock compatible with PowerMock (even though that would be nice) but to explain what is happening here and how I can work around it. I assume the problem is related to Groovy code transformations taking place in Spock tests or maybe something about delegates, meta classes or how methods are discovered and called in general. The whole area of Groovy delegates, meta classes etc. is not my area of expertise, so I am asking, sorry.

Is there any way I can stop Spock from actually evaluating the original static method and instead doing whatever ought to be done in order to get these calls under PowerMock's control?

@kriegaex

This comment has been minimized.

Copy link
Author

commented Aug 24, 2019

I still found no explanation and also no workaround with mockStatic which I could later check interactions (number of calls) on with verifyStatic, but I did find a simple workaround for stubbing out the static methods:

package de.scrum_master.test

import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.spockframework.runtime.Sputnik
import spock.lang.Specification

import java.sql.Date

import static org.powermock.api.support.membermodification.MemberMatcher.method
import static org.powermock.api.support.membermodification.MemberModifier.stub

@RunWith(PowerMockRunner)
@PowerMockRunnerDelegate(Sputnik)
@PrepareForTest(ClassCallingStaticJREMethods)
class SpockTest extends Specification {
  def testUUID() {
    given:
    final String id = "493410b3-dd0b-4b78-97bf-289f50f6e74f";
    UUID uuid = UUID.fromString(id)
    stub(method(UUID, "randomUUID")).toReturn(uuid)
    ClassCallingStaticJREMethods underTest = new ClassCallingStaticJREMethods()

    expect:
    underTest.genUUID() == id
  }

  def testSQLDate() {
    given:
    def date = new Date(1971 - 1900, 5 - 1, 8)
    stub(method(Date, "valueOf", String)).toReturn(date)
    ClassCallingStaticJREMethods underTest = new ClassCallingStaticJREMethods()

    expect:
    underTest.genDate("1998-12-20") == date
  }
}

Please note that I need to prepare the class under test rather than the JRE classes with the static methods for test via @PrepareForTest(ClassCallingStaticJREMethods) as is described in the PowerMock wiki for stubbing static methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant
You can’t perform that action at this time.