1

In Groovy / Spock I can mock a class by doing:

def theClass = Mock(TheClass.class)

and then wire that instance into the class under unit test. What about if I want to mock a class that has been annotated as a @Singleon

More Than Five
  • 9,959
  • 21
  • 77
  • 127
  • 2
    Why would you need to mock a singleton? I've never seen a good reason to do so. – Szymon Stepniak Jul 02 '18 at 12:59
  • I am unit testing a specific class. It calls an API from the the singleton which goes off and gets a bunch of data from parsing a bunch of files and return a List of objects. I just want to stub out the the returned list of objects. – More Than Five Jul 02 '18 at 13:22
  • You accepted Leonard's answer. Why? It does not work. Can you please show how you did it? I was trying to replicate his solution, but it just does not work for code actually accessing `MySingleton.instance`. Wasn't that your use case? For that I need to use my helper class to explicitly change the singleton instance. – kriegaex Jul 06 '18 at 01:34

2 Answers2

3

Here is a little tool class you can use:

package de.scrum_master.stackoverflow

import java.lang.reflect.Field
import java.lang.reflect.Modifier

class GroovySingletonTool<T> {
  private Class<T> clazz

  GroovySingletonTool(Class<T> clazz) {
    this.clazz = clazz
  }

  void setSingleton(T instance) {
    // Make 'instance' field non-final
    Field field = clazz.getDeclaredField("instance")
    field.modifiers &= ~Modifier.FINAL
    // Only works if singleton instance was unset before
    field.set(clazz.instance, instance)
  }

  void unsetSingleton() {
    setSingleton(null)
  }

  void reinitialiseSingleton() {
    // Unset singleton instance, otherwise subsequent constructor call will fail
    unsetSingleton()
    setSingleton(clazz.newInstance())
  }
}

Just call setSingleton(Mock(TheClass)). For more info see this answer, I do not want to repeat the whole sample code here.

Feel free to ask follow-up questions if there is anything you do not understand.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
2

You can use global mocks

def publisher = new Publisher()
publisher << new RealSubscriber() << new RealSubscriber()

def anySubscriber = GroovyMock(RealSubscriber, global: true)

when:
publisher.publish("message")

then:
2 * anySubscriber.receive("message")
Leonard Brünings
  • 12,408
  • 1
  • 46
  • 66
  • 1
    I think this does not work because `RealSubscriber.instance` will be null. Furthermore, global mocks only work for Groovy classes. What if `Publisher` is a Java class? – kriegaex Jul 03 '18 at 02:02
  • The question had only the tags groovy and spock, so I assumed that the code in question is groovy code. If the target is used by java code it won't work. – Leonard Brünings Jul 05 '18 at 19:26
  • 1
    Maybe you are missing my point. It does not work with 100% Groovy code either due to the `@Singleton` annotation in the to-be-mocked class. The referring class tries to access it via `MySingleton.instance` and fails because it does not get the global Groovy mock but the original object. So I think there is no easy way around my helper class. – kriegaex Jul 06 '18 at 00:49