Hot questions for Using Mockito in singleton

Question:

I need to test some legacy code, which uses a singleton in a a method call. The purpose of the test is to ensure that the clas sunder test makes a call to singletons method. I have seen similar questions on SO, but all the answers require other dependencies (different test frameworks) - I'm unfortunately limited to using Mockito and JUnit, but this should be perfectly possible with such popular framework.

The singleton:

public class FormatterService {

    private static FormatterService INSTANCE;

    private FormatterService() {
    }

    public static FormatterService getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new FormatterService();
        }
        return INSTANCE;
    }

    public String formatTachoIcon() {
        return "URL";
    }

}

The class under test:

public class DriverSnapshotHandler {

    public String getImageURL() {
        return FormatterService.getInstance().formatTachoIcon();
    }

}

The unit test:

public class TestDriverSnapshotHandler {

    private FormatterService formatter;

    @Before
    public void setUp() {

        formatter = mock(FormatterService.class);

        when(FormatterService.getInstance()).thenReturn(formatter);

        when(formatter.formatTachoIcon()).thenReturn("MockedURL");

    }

    @Test
    public void testFormatterServiceIsCalled() {

        DriverSnapshotHandler handler = new DriverSnapshotHandler();
        handler.getImageURL();

        verify(formatter, atLeastOnce()).formatTachoIcon();

    }

}

The idea was to configure the expected behaviour of the dreaded singleton, since the class under test will call it's getInstance and then formatTachoIcon methods. Unfortunately this fails with an error message:

when() requires an argument which has to be 'a method call on a mock'.

Answer:

What you are asking is not possible because your legacy code relies on a static method getInstance() and Mockito does not allow to mock static methods, so the following line won't work

when(FormatterService.getInstance()).thenReturn(formatter);

There are 2 ways around this problem:

  1. Use a different mocking tool, such as PowerMock, that allows to mock static methods.

  2. Refactor your code, so that you don't rely on the static method. The least invasive way I can think of to achieve this is by adding a constructor to DriverSnapshotHandler that injects a FormatterService dependency. This constructor will be only used in tests and you production code will continue to use the real singleton instance.

    public static class DriverSnapshotHandler {
    
        private final FormatterService formatter;
    
        //used in production code
        public DriverSnapshotHandler() {
            this(FormatterService.getInstance());
        }
    
        //used for tests
        DriverSnapshotHandler(FormatterService formatter) {
            this.formatter = formatter;
        }
    
        public String getImageURL() {
            return formatter.formatTachoIcon();
        }
    }
    

Then, your test should look like this :

FormatterService formatter = mock(FormatterService.class);
when(formatter.formatTachoIcon()).thenReturn("MockedURL");
DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
handler.getImageURL();
verify(formatter, atLeastOnce()).formatTachoIcon();

Question:

Given a Kotlin singleton object and a fun that call it's method

object SomeObject {
   fun someFun() {}
}

fun callerFun() {
   SomeObject.someFun()
}

Is there a way to mock call to SomeObject.someFun()?


Answer:

There's a very nice mocking library for Kotlin - Mockk, which allows you to mock objects, the exact same way you're desiring.

As of its documentation:


Objects can be transformed to mocks following way:

object MockObj {
  fun add(a: Int, b: Int) = a + b
}

mockkObject(MockObj) // aplies mocking to an Object

assertEquals(3, MockObj.add(1, 2))

every { MockObj.add(1, 2) } returns 55

assertEquals(55, MockObj.add(1, 2))

To revert back use unmockkAll or unmockkObject:

@Before
fun beforeTests() {
    mockkObject(MockObj)
    every { MockObj.add(1,2) } returns 55
}

@Test
fun willUseMockBehaviour() {
    assertEquals(55, MockObj.add(1,2))
}

@After
fun afterTests() {
    unmockkAll()
    // or unmockkObject(MockObj)
}

Despite Kotlin language limits you can create new instances of objects if testing logic needs that:

val newObjectMock = mockk<MockObj>()

Question:

I have the following code:

trait Calculator {

  def add(x:Int, y:Int):Int
  def multiply(x:Int,y: Int):Int
}

trait MyCalculator extends Calculator {

  override def add(x: Int, y: Int): Int = x+y //in real live it calls remote service which is not avaialble in test

  override def multiply(x: Int, y: Int): Int = x*y //in real live it calls remote service which is not avaialble in test
}

object MyCalculator extends MyCalculator

Now I have Calculator service:

trait CalculatorServiceTrait {

  def calculate(x:Int,sign:String,y:Int):Int
}

trait CalculatorService extends CalculatorServiceTrait{
  override def calculate(x: Int, sign: String, y: Int): Int = {
    sign match{
      case "+" => MyCalculator.add(x,y)
      case "*" => MyCalculator.multiply(x,y)
      case _ => 0
    }
  }
}

object CalculatorService extends CalculatorService

Now I would like to mock MyCalculator using Mockito to bring me incorrect result.

 "Calculator Service" should{

    "return 0 when 2 and 2 used " in{

      val MyCalculatorMock = mock[MyCalculator]
      when(MyCalculatorMock.multiply(2,2)).thenReturn(0)
      class CalculatorServiceUnderTest extends CalculatorService with MyCalculator

      new CalculatorServiceUnderTest with MyCalculator
      val c = new CalculatorServiceUnderTest
      val result = c.calculate(2,"+",2)
      result shouldEqual(0)
    }
  }

but I'm still getting "4" instead of "0"

Is there any way to handle such test cases?

P.S: I can change some class or trait implementation, but doing global refactoring might be problematic


Answer:

Well, your mock object is not used anywhere, so, it's not a big surprise, that it never gets called, is it?

To answer your question, no, you cannot mock a singleton, and that is why it is almost never a good idea to use it directly like this. External dependencies to components need to be supplied from the outside in order for the components to be testable independently.

One way to do what you want is to make CalculatorService a class and pass the MyCalculator instance to the constructor as a parameter:

class CalculatorService(calc: MyCalculator = MyCalculator) 
   extends CalculatorServiceTrait {
     override def calculate(x: Int, sign: String, y: Int): Int = sign match {
       case "+" => calc.add(x,y)
       case "*" => calc.multiply(x,y)
       case _ => 0
    }
  }
}

then, in your test, you can just do: val testMe = new CalculatorService(mock[MyCalculator])

If it has to remain a trait for some reason, you can use "cake pattern" to supply external dependencies:

trait CalculatorProvider {
  def calc: MyCalculator
}

trait CalculatorService { self: CalculatorProvider => 
 ...
}

object CalculatorService extends CalculatorService with CalculatorProvider {
  def calc = MyCalculator
}


val testMe = new CalculatorService with CalculatorProvider {
  val calc = mock[MyCalculator]
}

Question:

I have lazy singleton MongoConnection class with a static method which returns MongoClient instance on MongoConnection.getClient():

 public class MongoConnection {

    private static MongoClient mongoclient;

    private MongoConnection() {

    }

    public static MongoClient getClient() {
        if (mongoclient == null) {
            // code to initialize MongoClient
        }
        return mongoclient;
    }
}

How do I use PowerMockito to mock MongoConnection singleton and test getClient method.

I don't have choice over singleton because there will be single instance of MongoClient across the application (as per MongoDB documentation).

Note: i don't want to connect to actual DB in test because it will be integration test rather than JUnit; I just want to make sure if MongoClient is initialized with expected parameters. How to do I achieve this with PockerMockito?

Thank you


Answer:

The following example shows you how to use Mockito with PowerMockito to mock your MongoConnection.getMongoClient():

@RunWith(PowerMockRunner.class)
@PrepareForTest({MongoConnection.class})
public class ATest {

    @Test
    public void aTestWhichRequiresMockingMongoConnection() {
        MongoClient mongoClient = Mockito.mock(MongoClient.class);
        PowerMockito.mockStatic(MongoConnection.class);

        Mockito.when(MongoConnection.getClient()).thenReturn(mongoClient);

        // set up some expectations on the mocked MongoClient returned by MongoConnection    
        Mockito.when(mongoClient.getDatabase("aDatabaseName")).thenReturn(...);

        // ... etc
    }
} 

This class is verified for:

  • Mockito v2.7.19
  • PowerMock v1.7.0
  • JUnit v4.12

As an aside, this:

i don't have choice over singleton because there will be single instance of MongoClient across the application(as per MongoDB documentation).

... does not mandate you to make your MongoClient static. You could make getClient() a non static method and ensure that MongoConnection is a singleton i.e. that your application only has single instance of it. Dependency injection solutions (such as Spring, Guice) have built-in support for ensuring that a dependency can be configured as a singleton.