Могу ли я издеваться над конструктором суперкласса с помощью Mockito / Powermock? - PullRequest
7 голосов
/ 31 марта 2012

Возможно ли использовать Mockito и, при желании, Powermock для макетирования суперкласса S так, чтобы любые вызовы суперкласса к S (включая вызовы конструктора S()) были имитированы? Поэтому, используя приведенный ниже пример, если я заменю S на MockS с помощью Mockito, вызовет ли super() конструктор из MockS?

class S {
   S() {
      // Format user's hard drive, call 911, and initiate self-destruct
   }
}

class T extends S {
   T() {
      super();
   }
}

class Test {
   @Mock private S mockS;
   new T(); // T's call to super() should call the mock, not the destructive S.
}

Я видел вопросы о том, чтобы издеваться над отдельными методами в S или только над вызовами super(), и читал, что это не поддерживается, но не ясно, могу ли я высмеивать весь суперкласс.

В моих текущих тестах, когда я пытаюсь смоделировать S, T вызов super() вызывает реальную реализацию, а не фиктив.

Ответы [ 3 ]

4 голосов
/ 31 марта 2012

Чтобы обойти это очевидное ограничение, я реорганизовал свой код, , заменив наследование делегированием , и я думаю, что в любом случае я получил лучший дизайн, поскольку наследование не было действительно необходимым.

Новый код выглядит следующим образом. Напомним, что код вопроса был упрощен, поэтому у реальных классов гораздо больше функциональности.

class S {
   S() {
      // Format user's hard drive, call 911, and initiate self-destruct
   }
}

class T {
   T(S s) {} // Now T "has an S" instead of "is an S"
}

class Test {
   @Mock private S mockS;
   new T(s); // T's call to super() should call the mock, not the destructive S.
}

Для тех, кто заинтересован, используя Guice и Android, тест выглядит примерно так:

class T {
   T(Activity activity, S s) {}
}

class Test {
  @Mock Activity activity;
  @Mock S mockS;
  injector = Guice.createInjector(new AbstractModule() {
     @Override protected void configure() {
        bind(Activity.class).toInstance(activity);
        bind(S.class).toInstance(mockS);
     }}
  );
  T t = injector.getInstance(T.class);
}
3 голосов
/ 31 марта 2012

Я думаю, что это возможно с PowerMock, только если метод на дочернем объекте отличается от метода на суперклассе (то есть, вы не можете высмеивать родительский метод, если дочерний метод переопределяет этот метод). Для более подробной информации, вы можете посмотреть соответствующий отчет об ошибке .

Для PowerMock проверьте страницу Подавление нежелательного поведения , чтобы увидеть, будет ли этого достаточно для ваших нужд.


После долгих размышлений я использовал JMockit для этих сложных случаев. Прежде чем перейти к JMockit, я попытался заглушить все места, где исключения были выброшены с использованием подавления. В конце концов, мне нужно было переопределить некоторые методы, а не просто подавить их, поэтому я отказался от них.

Пример использования для случая Android:

Сначала вы макетируете свой суперкласс, используя аннотацию @MockClass:

@MockClass(realClass = Activity.class, instantiation = PerMockedInstance)
public class FakeActivity {
    public Bundle mSavedInstanceState;

    @Mock
    public void $init() {}

    @Mock
    public void onCreate(Bundle savedInstanceState) {
        mSavedInstanceState = savedInstanceState;
    }
}

При активации этот класс заменит конструктор по умолчанию Activity на $init() и заменит метод onCreate на приведенный выше. С android тестируемый модуль получен из Activity (в моем примере кода это HelloTestActivity). Тестовый класс выглядит так:

public class HelloTestActivityTest3 extends AndroidTest {
    @Tested
    HelloTestActivity activity;

    FakeActivity fakeActivity = new FakeActivity();

    @Before
    public void setupMocks()
    {
        Mockit.setUpMock(fakeActivity);
    }

    @Test
    public void onCreate_bundle(@Mocked Bundle savedInstanceState)
    {
        // Try to access out-of-band information from the fake
        activity.onCreate(savedInstanceState);
        assertSame(savedInstanceState, fakeActivity.mSavedInstanceState);
    }
}

Звонок Mockit.setupMock(fakeActivity) заменяет суперкласс моим экземпляром подделки. При таком использовании вы также можете получить доступ к внутреннему состоянию вашего поддельного класса. Если вам не нужно переопределять какие-либо методы с пользовательскими функциями, вы можете использовать другие методы, доступные в Mockit class.

Как указал rogerio в комментариях ниже, насмешка над классом Activity - это минимум. Следующий код демонстрирует это.

public class HelloTestActivityTest4 {
    @Tested
    HelloTestActivity activity;

    @Mocked
    Activity base;

    @Test
    public void testOnCreate() throws Exception {
        // Just make sure "Stub!" exception is not thrown.
        activity.onCreate(null);
    }
}

Объявление @Mocked Activity base; вызывает все методы (за исключением статических инициализаторов) класса Activity и его суперклассов для проверки в тестах, определенных в HelloActivityTest4.

1 голос
/ 31 марта 2012

Что вы можете сделать, это извлечь «опасный» код в конструкторе суперкласса в не приватный метод, затем использовать шпион Mockito для вашего класса T и переопределить поведение в этом извлеченном методе.

Это, конечно, нарушит инкапсуляцию. Guava предлагает аннотацию VisibleForTesting для таких случаев.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...