27

I am unsure on how to mock an enum singleton class.

public enum SingletonObject{
  INSTANCE;
  private int num;

  protected setNum(int num) {
    this.num = num;
  }

  public int getNum() {
    return num;
  }

I'd like to stub getNum() in the above example, but I can't figure out how to mock the actual SingletonObject class. I thought using Powermock to prepare the test would help since enums are inherently final.

//... rest of test code
@Test
public void test() {
  PowerMockito.mock(SingletonObject.class);
  when(SingletonObject.INSTANCE.getNum()).thenReturn(1); //does not work
}

This is using PowerMockMockito 1.4.10 and Mockito 1.8.5.

Sonwright Gomez
  • 271
  • 1
  • 3
  • 3
  • Check similar thread : http://stackoverflow.com/questions/2302179/mocking-a-singleton-class – Martin V. Apr 11 '13 at 01:08
  • Thanks Martin, I read through that thread and it looks like its an approach for using the non-enum way of implementing a singleton, and I am able to use mocking properly with that method. However, is there a way to mock an enum singleton class? From what I've gathered, enum singleton class is the recommended method of declaring a singleton after java 1.5. – Sonwright Gomez Apr 11 '13 at 02:27
  • [Re: PowerMock : can i mock enums?](https://groups.google.com/forum/?fromgroups=#!topic/powermock/hqrJaMi0Zrc) – Joe Apr 15 '13 at 13:27
  • Singletons are evils :) – MariuszS Apr 26 '13 at 20:16

5 Answers5

33

If you want to stub out what INSTANCE returns, you can do it but it's kind of nasty (using reflection & bytecode manipulation). I created & tested a simple project with three classes using the PowerMock 1.4.12 / Mockito 1.9.0. All classes were in the same package.

SingletonObject.java

public enum SingletonObject {
    INSTANCE;
    private int num;

    protected void setNum(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }
}

SingletonConsumer.java

public class SingletonConsumer {
    public String consumeSingletonObject() {
        return String.valueOf(SingletonObject.INSTANCE.getNum());
    }
}

SingletonConsumerTest.java

import static org.junit.Assert.*;
import static org.powermock.api.mockito.PowerMockito.*;

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

@RunWith(PowerMockRunner.class)
@PrepareForTest({SingletonObject.class})
public class SingletonConsumerTest {
    @Test
    public void testConsumeSingletonObject() throws Exception {
        SingletonObject mockInstance = mock(SingletonObject.class);
        Whitebox.setInternalState(SingletonObject.class, "INSTANCE", mockInstance);

        when(mockInstance.getNum()).thenReturn(42);

        assertEquals("42", new SingletonConsumer().consumeSingletonObject());
    }
}

The call to the Whitebox.setInternalState replaces INSTANCE with a mock object that you can manipulate within your test.

Matt Lachman
  • 4,541
  • 3
  • 30
  • 40
  • 2
    But how can you mock SingletonObject.class? It will give MockitoException with comment "Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types" – Thamiar Nov 19 '15 at 11:06
  • 1
    This uses PowerMock. – Matt Lachman Apr 25 '16 at 15:51
  • Hi, i got `java.lang.IllegalAccessException: Can not set static final` while using `Whitebox.setInternalState`. Do you know why? – elsennov Sep 08 '16 at 06:04
  • 1
    Did you use the `@PrepareForTest` annotation on your test class? – Matt Lachman Sep 09 '16 at 18:56
  • 2
    I tried the same way but my enum's constructor is still getting executed, which I don't want. Does above solution mock's the enum's constructor as well? – Shruts_me Apr 16 '17 at 06:25
  • At the time of the writing of this answer, the above would have worked. Four years later, I am not sure how updates to Java, PowerMock, and Mockito would play together. One thing to check are your annotations. – Matt Lachman Apr 28 '17 at 15:44
1

Have an interface with the methods you intend to mock

public interface SingletonInterface {
  int getNum();
}

Let the enum implement the interface

public enum SingletonObject implements SingletonInterface {
    INSTANCE;
    private int num;

    protected void setNum(int num) {
        this.num = num;
    }

    @Override
    public int getNum() {
        return num;
    }
}

Mock the interface

@Test
public void test() {
  SingletonInterface singleton = Mockito.mock(SingletonInterface.class);
  when(singleton.getNum()).thenReturn(1); //does work
}
JavaExcel
  • 21
  • 1
0

In addition to above Matt Lachman answer, create a object factory for power mock in SingleTonConsumerTest.class

@ObjectFactory
public IObjectFactory getObjectFactory() {
    return new org.powermock.modules.testng.PowerMockObjectFactory();
}

This will remove Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types error.

0

I am not sure if this is best approach, but in my case it's working without troubles so far. I am using mockito-core and mockito-inline (3.11.0) and I am mocking my singleton with help of spy. Example with your class:

SingletonObject so = SingletonObject.INSTANCE;
private SingletonObject spySo = Mockito.spy(so);

@Before
public void setUp() {
    Mockito.doReturn(10).when(spySo).getNum();
}

@Test
public void simpleTest() {
    assertThat(spySo.getNum(), is(10));
}
PetKap
  • 31
  • 6
0

Mockito docs are not explicit on enum use case:

Powermock support:

  • Compatible with JUnit 4

build.gradle (** should work with Maven too)

    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
    testImplementation 'org.mockito:mockito-inline:4.0.0'
    testImplementation 'org.mockito:mockito-junit-jupiter:4.1.0'

As of the available experimental features (Powermock + JUnit 5), can give a try to:

    implementation group: 'org.powermock', name: 'powermock-module-junit5', version: '1.6.4'
    testImplementation 'org.powermock:powermock-mockito-release-full:1.6.4'

Enum:

public enum ConnectionFactory {
   INSTANCE;

   public Connection get() throws SQLException {
      //
      // Load url from configuration file  ('application.yml')
      //
      final var connection = DriverManager.getConnection(url);
      connection.setAutoCommit(false);

      return connection;
   }
}

Can use these approaches:

class DatabaseUnitTests {

   private final ConnectionFactory connectionFactory = Mockito.spy(ConnectionFactory.INSTANCE);


   //Or

   @InjectMocks
   private final ConnectionFactory connectionFactory = Mockito.mock(ConnectionFactory.class);


   @Test
   void check_is_database_connection_is_OK(){
      Mockito.doReturn(mockedConnection()).when(connectionFactory).get();
       
      // Do something
   }

Felix Aballi
  • 899
  • 1
  • 13
  • 31