ThCollignon
ThCollignon

Reputation: 1204

How to mock an instance created by a static method?

I'm testing legacy code that looks like this.

class Converter {
  static String convert() {
    SubConverter.subConvert();
  }
}
class SubConverter {
  static String subConvert() {
    Generator generator = new Generator();
    return generator.generate();
  }
}

My goal is to test Converter.convert, mocking generator.generate().

The test class looks like this:

import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

class ConverterTest {

  @Mock
  Generator generatorMock;
  
  @Test
  public void convertTest(){
    MockitoAnnotations.openMocks(this);
    Mockito.when(generatorMock.generate()).thenReturn("123");
    Converter.convert();
    // some assertions
  }
}

The actual implementation of Generator.generate() is used instead of the mock.

I've tried:

import org.mockito.InjectMocks;

@InjectMocks
Converter converterWithMock;

, testing converterWithMock.convert(), same result.

I've tried with PowerMockito, I can't make it work (maybe I use it the wrong way).

What's the right way to do this?

Using junit-jupiter 5.8.2 and mockito 4.3.1.

Upvotes: 2

Views: 2015

Answers (1)

Eugene
Eugene

Reputation: 5985

You need to mock constructor of Generator class.


Solution 1: Using Mockito (Main preferable)
Mockito suports mock construction since 3.5 version. Documentation
Add mockito-inline to your dependencies. It provides ability to generate mocks on constructor invocations.

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>4.3.1</version>
            <scope>test</scope>
        </dependency>

Mock constructor.

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import org.mockito.Mockito;

import static org.mockito.Mockito.*;

class ConverterTest {
    @Test
    public void convertTest(){
        //create mock for Generator class constructor
        try (MockedConstruction<Generator> mockedGenerator = Mockito.mockConstruction(Generator.class,
                (mock, context) -> {
                    when(mock.generate()).thenReturn("123");
                })) {

            //execute Converter class under the test
            String result = Converter.convert();
            
            //ensure that constructor was executed
            Assertions.assertFalse(mockedGenerator.constructed().isEmpty());
            //ensure the generate method was executed
            verify(mockedGenerator.constructed().get(0), times(1)).generate();
            //verify return value
            Assertions.assertEquals("123", result);
        }
    }
}

Solution 2: Using PoserMock
PowerMock does not have JUnit 5 support, only Junit 4.
Example of mocking constructor via PowerMock

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(SubConverter.class)
public class ConverterTest {

    @Mock
    Generator generatorMock; //create Mock

    @Test
    public void convertTest() throws Exception {
        //configure Mock
        Mockito.when(generatorMock.generate()).thenReturn("123");
        //return mock when constructor is executing
        PowerMockito.whenNew(Generator.class).withNoArguments().thenReturn(generatorMock);

        //perform Converter under the test 
        String result = Converter.convert();

        //check return value
        Assert.assertEquals("123", result);
    }
}

Upvotes: 2

Related Questions