2

I attempted to override the java.time.Year class's public static method now() because I want it to return a different value, not just the current year. In my productive code, I have a method which I am testing and it is using the Year.now() method as:

Java class under test

public class SomeClass {

  public LocalDate methodUnderTest() {
    return Year.now()
        .atMonth(JANUARY)
        .atDay(1);
  }

}

In the Spock groovy test, I attempted to override the java.time.Year.now() method this way: I intend that the overriden behaviour of the static method now() is used in my productive code, in the method methodUnderTest()

class SomeClassSpec extends Specification {

  @Subject
  SomeClass classUnderTest = new SomeClass()

  def "test method with overriden behaviour"() {

    setup:
    Year.metaClass.'static'.now = { -> Year.of(year) }

    expect:
    classUnderTest .methodUnderTest() == expectedDate

    where:
    year           || expectedDate
    2019           || LocalDate.of(2019, JANUARY, 1)
    2020           || LocalDate.of(2020, JANUARY, 1)
    2021           || LocalDate.of(2021, JANUARY, 1)
  }
}

However, the test only works for the year 2020 (the current year), the other 2 values 2019 and 2021 are not working because it seems that the intended overriden behaviour is obviously not used in the productive code. Why is the intended overriden behaviour not used in the productive code of methodUnderTest() at runtime?

Patrick
  • 21
  • 1

1 Answers1

1

Some basics:

  1. Groovy meta-class stuff only works for Groovy classes, not for Java classes. I.e. the Java class knows nothing about any defined meta-class overrides, only Groovy calling your Java class code does.

  2. The JRE bootstrap class java.time.Year is final, i.e. you cannot by conventional means create mock instances. You would need to un-finalise the class using a special class-loaderwhile it is loaded. My own tool Sarek which is still under development offers this feature. Others like PowerMock provide more indirect ways of helping you to stub final classes by instrumenting the classes which call them.

  3. The method Year.now() which you want to stub is not just in a final class but also static. Spock does not have any on-board means of stubbing static methods except for Groovy methods. Again, your method is in a Java class and also called by a Java class, so that is not going to help you. Again Sarek or other tools such as PowerMock can help you there.

Here is a little sample for doing it in Sarek. I have just pushed a snapshot to Maven Central for you, so you should be able to use that.

    <dependency>
      <groupId>dev.sarek</groupId>
      <artifactId>sarek</artifactId>
      <version>1.0-SNAPSHOT</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>dev.sarek</groupId>
      <artifactId>sarek-spock-extension</artifactId>
      <version>1.0-SNAPSHOT</version>
      <scope>test</scope>
    </dependency>
package de.scrum_master.stackoverflow.q65321086;

import java.time.LocalDate;
import java.time.Year;

import static java.time.Month.JANUARY;

public class SomeClass {
  public LocalDate methodUnderTest() {
    return Year.now()
      .atMonth(JANUARY)
      .atDay(1);
  }
}
package de.scrum_master.stackoverflow.q65321086

import dev.sarek.agent.mock.MockFactory
import spock.lang.Specification
import spock.lang.Subject
import spock.lang.Unroll

import java.time.LocalDate
import java.time.Year

import static java.time.Month.JANUARY
import static net.bytebuddy.matcher.ElementMatchers.named

class SomeClassTest extends Specification {
  @Subject
  SomeClass classUnderTest = new SomeClass()

  @Unroll
  def "override static JRE method Year.now() for #year"() {
    setup:
    MockFactory<Year> mockFactory = MockFactory
      .forClass(Year.class)
      .mockStatic(
        named("now"),
        { method, args -> false },
        { method, args, proceedMode, returnValue, throwable -> Year.of(year) }
      )
      .build()

    expect:
    classUnderTest.methodUnderTest() == expectedDate

    cleanup:
    mockFactory.close()

    where:
    year || expectedDate
    2019 || LocalDate.of(2019, JANUARY, 1)
    2020 || LocalDate.of(2020, JANUARY, 1)
    2021 || LocalDate.of(2021, JANUARY, 1)
  }
}

This test passes for me. There is no in-depth documentation or tutorial for Sarek yet other than pretty good JavaDoc (check the sources) and lots of sample tests in different modules.

As for Sarek, I am planning to integrate it into Spock even better than now so using it will feel mock "spocky" in the future. I have just been very busy lately. But it is fully usable already and also comes with integrations into JUnit 4, Junit 5, TestNG.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • **Update:** Previously I had forgotten to upload a Sarek snapshot to Maven Central because I had some authentication issues which are now fixed. Now you are good to go if you want to use Sarek. – kriegaex Dec 17 '20 at 12:05