question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

GroovyMock/Stub constructors return null -> undocumented caveat

See original GitHub issue

Preface

Original content taken from my StackOverflow answer. I am copying both question and answer here for your convenience.

I would like to know if it is intended that for a global Groovy mock/stub constructor calls to the target class return null instead of a mock object. I find it counter-intuitive, it is completely undocumented and also has zero test coverage in the Spock source code.

Original StackOverflow question

I’m using the following test as an example to showcase a similar issue I’m seeing. I think it’s just a misunderstanding on my part about how global mocks work in SpockFramework.

  void "test"() {
    when:
    TestStage stage = new TestStage("John")
    GroovyMock(TestStep.class, global: true) {
      getName() >> "Joe"
    }
    then:
    stage.run() == "Joe"
  }

This test should create a test stage supplying a default name. But then I create a global mock of a class inside of TestStage to override the return value. IE: I’m only trying to test the functionality of TestStage not TestStep. If TestStep were to make only changes, I don’t want to know about them, I’ll test those separately. However when I run this test, it looks like the global mock never takes effect as the returned name is still "John", which is what I supplied originally.

stage.run() == "Joe"
|     |     |
|     John  false

Here’s the two sample classes used to test this.

class TestStage {
  TestStep step

  TestStage(String name) {
    this.step = new TestStep(name)
  }

  String run() {
    return step.getName()
  }
}
class TestStep {
    private String name

    TestStep(String name) {
      this.name = name
    }

    String getName() {
      return this.name
    }
}

Original StackOverflow answer

Actually, you are asking a good question here because according to the Spock manual it seems as if you could use GroovyMock and GroovyStub in order to globally replace instances and stub their methods as you tried to do, even though if I were you I would have created the global mock object first before implicitly using it in the constructor of the object depending on it. But anyway, it does not work as expected, like you said.

When I searched the Spock manual and the Spock source code for examples concerning GroovyMock, nowhere did I find a single one involving anything else than static methods. The test coverage there is quite bad, actually. Usually if the manual does not help me, I look if I can infer from the tests how to use a feature. In this case I had to try by myself.

The first thing I noticed is the completely counter-intuitive fact that when calling a constructor on a global GroovyMock or GroovyStub, it returns null!!! This is a real caveat. In a way constructors are treated like normal mock methods here, also returning null. Nowhere does any official source mention that and I also think it should be changed to default to returning a normal Spock mock instead (if the class is mockable, i.e. non-final).

Now this is also the key to the solution: You need to stub one or more constructors to return something else than null, e.g. a previously created normal instance or a Spock mock/stub/spy.

Here is a slightly altered version of your source code (I renamed the application classes so as not to contain “Test” in their names because all those "Test"s were a little confusing to me, especially because I also named my test class “*Test”, as I usually do instead of “*Spec” because then Maven Surefire/Failsafe pick it up automatically without extra configuration.

I also added a static method to the class to be mocked in order to show you the syntax for stubbing that too. that is just a free add-on and not directly related to your question.

My test shows three variants:

  • using a classical Spock mock and injecting it into the subject under test
  • using a global GroovySpy which is always based on a real object (or instructed to create one) and thus you do not need to stub a contructor
  • using a global GroovyMock with an explicitly stubbed constructor, in my example returning a regular Spock mock with a stubbed method, but it could also return a normal instance.
package de.scrum_master.stackoverflow.q61667088

class Step {
  private String name

  Step(String name) {
    this.name = name
  }

  String getName() {
    return this.name
  }

  static String staticMethod() {
    return "original"
  }
}
package de.scrum_master.stackoverflow.q61667088

class Stage {
  Step step

  Stage(String name) {
    this.step = new Step(name)
  }

  String run() {
    return step.getName()
  }
}
package de.scrum_master.stackoverflow.q61667088

import spock.lang.Specification

class GlobalMockTest extends Specification {

  def "use Spock mock"() {
    given:
    def step = Mock(Step) {
      getName() >> "Joe"
    }
    def stage = new Stage("John")
    stage.step = step

    expect:
    stage.run() == "Joe"
  }

  def "use global GroovySpy"() {
    given:
    GroovySpy(Step, global: true) {
      getName() >> "Joe"
    }
    Step.staticMethod() >> "stubbed"
    def stage = new Stage("John")

    expect:
    Step.staticMethod() == "stubbed"
    stage.run() == "Joe"
  }

  def "use global GroovyMock"() {
    given:
    GroovyMock(Step, global: true)
    new Step(*_) >> Mock(Step) {
      getName() >> "Joe"
    }
    Step.staticMethod() >> "stubbed"
    def stage = new Stage("John")

    expect:
    Step.staticMethod() == "stubbed"
    stage.run() == "Joe"
  }

}

P.S.: I think you probably read the Spock manual, but just in case: If your GroovyMock/Stub/Spy target is implemented in a language other than Groovy such as Java or Kotlin, this will not work because then Groovy* will behave like a regular Spock mock/stub/spy.

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:3
  • Comments:9 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
leonard84commented, Jul 14, 2021

It is all a matter of priority and resources, and as this is an issue that doesn’t affect many users it isn’t that high on my list, that doesn’t mean it is not valid.

1reaction
kriegaexcommented, May 8, 2020

The normal use case for most people should be that they cannot directly inject a dependency into a class under test because there is no property, setter or constructor parameter for it. Then they expect that a dummy is automatically created upon each constructor call unless otherwise specified by overriding the constructor with something more specific. What the user intuitively expects when he writes something like this…

GroovyMock(FooType, global: true) {
  doSomething(_) >> "stub result"
}

… is that a Spock mock with one stubbed method is created globally, not that whenever a FooType is created the result is null. ZeroOrNullResponse for the constructor does not make any intuitive sense there as a default, only for the methods (both static and non-static).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Can constructor return a null object? - java - Stack Overflow
The code is dead in any version of Java. It's not possible for a constructor to return null , and even if an...
Read more >
Return null instance when constructor parameter is null
Obviously constructors cannot return a value, but I want class instance to be a null if parameters do not match. Does it make...
Read more >
Why do Constructors return null when used within EditorWindow
I'm writing a Popup-ytype window to be used for creating "Effects" for character skills an Items and this Bizzare behaviours starts ...
Read more >
Stop Returning Null in Java - Code by Amir | Amir Boroumand
In the absence of a constructor, the getArticles() and getName() methods will return a null reference. Writing methods that return null ...
Read more >
FAQ (null safety) - Dart
FAQs to help you migrate your Dart code to null safety. ... If the intent of the factory was indeed to return null,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found