Search code examples
groovymockingspocknon-static

Does a GroovyMock of a non-static method have a specified behaviour?


I put this in my Spock test:

GroovyMock( File, global: true)
File.createNewFile() >> null

... which I realise is unorthodox/silly/curious: createNewFile is a non-static method.

The code involved is like this:

if( indexInfoFile.createNewFile() ) {

... it turns out from my experiements that mocking createNewFile like this always returns false, even if you try putting a block in the mock:

GroovyMock( File, global: true)
File.createNewFile() >> {
    log.info( 'Hello mum!')
}

... the log message is not printed but createNewFile again returns false.

This is actually what I wanted (i.e. to mock a false return from createNewFile).

Is this intentional, documented behaviour?

PS Caveat: from my experience/experiments today, there is no doubt that this mock method does replace all occurrences of an invocation of this method, on any File instance. However, it appears also to have some alarming side-effects too: for example, a directory I created in my given block before the 2 GroovyMock lines is found NOT to exist afterwards, still in the given block, when I went

myDirPath.toFile().exists()

... I assume this is because toFile involves an invocation of createNewFile...


Solution

  • As documented, Groovy mocks only have additional "magic" when used with Groovy classes, but I assume that you are trying to mock java.io.File, which is a Java JRE class. Thus, the Groovy mock will behave like a normal Spock mock. So I don't know why you want to use the Groovy mock in the first place - maybe because you want to use the global: true feature in order to avoid refactoring for testability in your application class.

    As you do not show us an MCVE, I have no way of knowing whether indexInfoFile can be injected into your class/method under test or if it is a dependency created inside the method. In the latter case you need to refactor, it is as simple as that. Dependencies should be injectable, period.

    As for your code snippets, there are a few things wrong with them:

    • Method File.createNewFile() returns boolean, so it does not make any sense to stub it to return null.
    • When creating a mock, all methods will automatically return false, null or 0, depending on their return type. So there is no need to stub the result for createNewFile() in the first place if you want it to return false because it already does.
    • You cannot stub an instance method by trying to override it like it was a static method. It makes no sense. Please learn Spock syntax first.

    Now, assuming your class under test looks like this (already prepared or refactored for dependency injection via method argument, constructor argument or setter)...

    package de.scrum_master.stackoverflow.q59842227;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.Random;
    
    public class FileCreator {
      private static final Random RANDOM = new Random();
    
      public boolean createIndexInfoFile(File indexInfoFile) throws IOException {
        if (indexInfoFile.createNewFile()) {
          System.out.println("File \"" + indexInfoFile + "\" created");
          return true;
        }
        System.out.println("File \"" + indexInfoFile + "\" NOT created");
        return false;
      }
    
      public static void main(String[] args) throws IOException {
        new FileCreator().createIndexInfoFile(
          new File("_abc_" + RANDOM.nextInt(10000) + ".txt")
        );
      }
    }
    

    ... then you can test it like this:

    package de.scrum_master.stackoverflow.q59842227
    
    import spock.lang.Specification
    
    class FileCreatorTest extends Specification {
      def "index info file created"() {
        given:
        File file = Mock() {
          createNewFile() >> true
        }
    
        expect:
        new FileCreator().createIndexInfoFile(file)
      }
    
      def "no index info file created"() {
        given:
        File file = Mock()
    
        expect:
        !new FileCreator().createIndexInfoFile(file)
      }
    }
    

    See? There is no need for global or Groovy mocks, normal mocks will do just fine. But you need to make your code testable instead of using fancy tricks.