Mockito Как имитировать только вызов метода суперкласса

95

Я использую Mockito в некоторых тестах.

У меня есть следующие занятия:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

Я хочу высмеять только второй вызов ( super.save) из ChildService. Первый вызов должен вызывать настоящий метод. Есть способ сделать это?

мада
источник
Можно ли это решить с помощью PowerMockito?
javaPlease42
@ javaPlease42: Да, вы можете: stackoverflow.com/a/23884011/2049986 .
Jacob van Lingen

Ответы:

57

Нет, Mockito этого не поддерживает.

Возможно, это не тот ответ, который вы ищете, но то, что вы видите, является признаком неприменения принципа дизайна:

Предпочитайте композицию наследованию

Если вы извлечете стратегию вместо расширения суперкласса, проблема исчезнет.

Однако, если вам не разрешено изменять код, но вы все равно должны его протестировать, и таким неудобным способом, надежда еще есть. С помощью некоторых инструментов АОП (например, AspectJ) вы можете вплести код в метод суперкласса и полностью избежать его выполнения (фу). Это не работает, если вы используете прокси, вам нужно использовать модификацию байт-кода (либо изменение времени загрузки, либо изменение времени компиляции). Существуют фиктивные фреймворки, которые также поддерживают этот тип трюков, например PowerMock и PowerMockito.

Я предлагаю вам заняться рефакторингом, но если это не вариант, вас ждут серьезные взломы.

Айвейн
источник
5
Я не вижу нарушения LSP. У меня примерно такая же настройка, что и у OP: базовый класс DAO с методом findAll () и подкласс DAO, который переопределяет базовый метод, вызывая super.findAll () и затем сортируя результат. Подкласс можно заменить во всех контекстах, принимающих суперкласс. Я неправильно понимаю, что вы имеете в виду?
1
Я удалю замечание LSP (это не добавляет ценности ответу).
iwein 02
Да, наследование - отстой, и глупая структура, на которой я застрял, разработана с наследованием в качестве единственного варианта.
Шридхар Сарнобат
Предполагая, что вы не можете перепроектировать суперкласс, вы можете извлечь //some codesкод в метод, который можно протестировать отдельно.
Phasmal
1
Хорошо понял. Это другая проблема, чем то, что я пытался решить, когда искал это, но, по крайней мере, это недоразумение решило мою собственную проблему - имитировать вызов из базового класса (который я действительно не отменяю).
Гийом Перро,
89

Если у вас действительно нет выбора для рефакторинга, вы можете имитировать / заглушить все в вызове супер-метода, например

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }
джгонианин
источник
2
этот код на самом деле не препятствует вызову super.save (), поэтому, если вы много делаете в super.save (), вам придется предотвращать все эти вызовы ...
iwein
фантастическое решение творит чудеса для меня, когда я хочу вернуть фиктивное значение из метода суперкласса для использования ребенком, фантастическое спасибо.
Gurnard
14
это хорошо работает, если проверка не скрыта или метод сохранения не выполняет работу напрямую, а не вызывает другой метод. mockito не делает: Mockito.doNothing (). when ((BaseService) spy) .save (); это не будет делать ничего в сохранении базовой службы, а в сохранении службы childService :(
tibi
Я не могу заставить это работать на моем - он также заглушает дочерний метод. BaseServiceабстрактно, хотя я не понимаю, почему это имеет значение.
Шридхар Сарнобат
1
@ Sridhar-Sarnobat, да, я вижу то же самое :( Кто-нибудь знает, как заставить его только super.validate()
заглушить
5

Рассмотрите возможность рефакторинга кода из метода ChildService.save () в другой метод и протестируйте этот новый метод вместо тестирования ChildService.save (), таким образом вы избежите ненужного вызова супер-метода.

Пример:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 
Мохаммед Мисбахуддин
источник
1

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

Люк
источник
Могу ли я также сказать, что композиция по сравнению с наследованием почти всегда лучше, но иногда просто проще использовать наследование. пока java не будет включать лучшую композиционную модель, такую ​​как scala или groovy, так будет всегда, и эта проблема будет продолжать существовать
Люк,
1

Даже если я полностью согласен с ответом iwein (

предпочитать композицию наследованию

), я признаю, что иногда наследование кажется естественным, и я не чувствую, что его нарушают или реорганизуют только ради модульного теста.

Итак, мое предложение:

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}

А затем в модульном тесте:

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done
плотины50
источник
0

Причина в том, что ваш базовый класс не является общедоступным, тогда Mockito не может его перехватить из-за видимости, если вы измените базовый класс как общедоступный или @Override в подклассе (как общедоступный), тогда Mockito может правильно издеваться над ним.

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}
зуфей
источник
8
Дело не в этом. ChildService должен перекрывать foo (), и проблема в том, как имитировать BaseService.foo (), но не ChildService.foo ()
Адриан Костер
0

Возможно, самый простой вариант, если наследование имеет смысл, - это создать новый метод (частный пакет ??) для вызова super (давайте назовем его superFindall), шпионить за реальным экземпляром, а затем издеваться над методом superFindAll () так, как вы хотели имитировать родительский класс один. Это не идеальное решение с точки зрения охвата и видимости, но оно должно работать и его легко применять.

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

    void superSave(){
        super.save();
    }
}
Rubasace
источник