Перехват реальных вызовов нестатических c методов с помощью Mockito - PullRequest
0 голосов
/ 09 мая 2020

Есть ли способ с помощью Mockito или PowerMockito перехватывать вызовы нестатических c методов объекта или хотя бы одноэлементного объекта?

Приведен пример следующими классами:

public class Singleton {

  private static Singleton INSTANCE = null;

  private Singleton(Object parameter) {}

  public static Singleton getInstance(Object parameter) {
    if (INSTANCE == null) {
      INSTANCE = new Singleton(parameter);
    }
    return INSTANCE;
  }

  public String process(String a, String b) {
    return (a + b);
  }

  // Other methods
}

public class Foreign {

  private Foreign() {}

  public static void main(String[] args) {
    System.out.println(Singleton.getInstance(new Object()).process("alpha", "beta"));
  }
}

Объект Singleton создается в классе Foreign вне контроля некоторого тестового кода (не показан выше). Ни один из этих двух классов не может быть изменен. Цель состоит в том, чтобы перехватить вызовы нестатического c process() метода в тестовом коде, чтобы для определенных значений возвращался другой результат, например вызов

Singleton.getInstance(new Object()).process("alpha", "beta");

имитируемый для возврата "alpha-beta" вместо ожидаемого "alphabeta".

Одним из решений может быть перехват метода Singleton.getInstance() для создания экземпляра настраиваемого подкласса Singleton, например, с использованием

public class SubSingleton extends Singleton {

  public SubSingleton(Object parameter) {
    super(parameter);
  }

  public String process(String a, String b) {
    if ("alpha".equals(a) && "beta".equals(b)) {
      return a + "-" + b;
    }
    return super.process(a + b);
  }
}

Затем вызывает к методу Singleton.process() будет перехвачен, как в:

Object parameter = new Object();
PowerMockito.doReturn(new SubSingleton(parameter)).when(Singleton.class, "getInstance", parameter);

Однако приведенный выше класс Singleton предоставляет только частный конструктор, поэтому его нельзя расширить. Использование PowerMockito.whenNew() для возврата частичного имитации (шпиона) также не будет работать, поскольку класс Singleton не предоставляет конструктор без аргументов.

Можно ли реализовать желаемый имитатор каким-либо другим способом? Можно ли это сделать для не-одиночных классов?

1 Ответ

1 голос
/ 10 мая 2020

Во-первых, вы можете использовать whenNew для объектов с конструктором с некоторыми параметрами:

@RunWith(PowerMockRunner.class)
@PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {

    @Mock
    Singleton singletonMock;

    @Before
    public void setUp() throws Exception {
        PowerMockito.whenNew(Singleton.class)
                .withAnyArguments()
                .thenReturn(singletonMock);
    }

    @Test
    public void testMockNew() throws Exception {
        Mockito.when(singletonMock.process(anyString(), anyString())).thenReturn("sasa");
        Foreign.main(new String[0]);
    }
}

Во-вторых, почему бы не заглушить getInstance вместо new:

@RunWith(PowerMockRunner.class)
@PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {

    @Test
    public void testMockNew() {
        PowerMockito.mockStatic(Singleton.class);
        Singleton singletonMock = Mockito.mock(Singleton.class);
        PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
        Mockito.when(singletonMock.process(anyString(), anyString())).thenReturn("sasa");
        Foreign.main(new String[0]);
    }
}

В-третьих, чтобы перехватить метод процесса:

  • создать настоящий синглтон
  • создать фиктивный синглтон
  • макет stati c getInstance, чтобы вернуть макет. ПРИМЕЧАНИЕ: вы должны вызвать mockStati c после получения реального экземпляра.
  • используйте thenAnswer, чтобы проверить аргументы на process call
    • вернуть желаемый ответ, если они соответствуют желаемому шаблону
    • иначе вызовите реальный метод на реальном синглтоне
@RunWith(PowerMockRunner.class)
@PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {

    @Test
    public void testMockNew() {
        var singletonReal = Singleton.getInstance(new Object());
        var singletonMock = Mockito.mock(Singleton.class);
        PowerMockito.mockStatic(Singleton.class);
        PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
        Mockito.when(singletonMock.process(anyString(), anyString())).thenAnswer((args) -> {
            String a = args.getArgument(0);
            String b = args.getArgument(1);
            if ("alpha".equals(a) && "beta".equals(b)) {
                return "sasa";
            } else {
                return singletonReal.process(a, b);
            }
        });
        Foreign.main(new String[0]);
    }
}

И, наконец, используйте шпион вместо имитации

@RunWith(PowerMockRunner.class)
@PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {

    @Test
    public void testMockNew() {
        var singletonReal = Singleton.getInstance(new Object());
        var singletonMock = Mockito.spy(singletonReal);
        PowerMockito.mockStatic(Singleton.class);
        PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
        Mockito.when(singletonMock.process("alpha", "beta")).thenReturn("sasa");
        // NOTE: real method is called for other args
        Foreign.main(new String[0]);
    }
}

...