Mockito Як знущатися над викликом методу суперкласу


94

У деяких тестах я використовую 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 .
Якоб ван Лінген,

Відповіді:


57

Ні, Mockito цього не підтримує.

Можливо, це не відповідь, яку ви шукаєте, але те, що ви бачите, є симптомом незастосування принципу дизайну:

Складайте перевагу над спадщиною

Якщо ви витягнете стратегію замість розширення супер класу, проблема зникне.

Якщо, однак, вам не дозволено змінювати код, але ви все одно повинні його протестувати, і таким незграбним чином все-таки є надія. За допомогою деяких інструментів AOP (наприклад, AspectJ) ви можете вплести код у метод суперкласу та повністю уникнути його виконання (юк). Це не працює, якщо ви використовуєте проксі-сервери, вам доведеться використовувати модифікацію байт-коду (або переплетення часу завантаження, або компіляція переплетення часу). Існують глузливі фреймворки, які також підтримують такий тип фокусів, як PowerMock та PowerMockito.

Я пропоную вам піти на рефакторинг, але якщо це не варіант, ви серйозно роздивляєтесь.


5
Я не бачу порушення LSP. У мене приблизно така ж установка, як і у OP: базовий клас DAO з методом findAll () та підклас DAO, який замінює базовий метод, викликаючи super.findAll (), а потім сортуючи результат. Підклас можна замінити у всіх контекстах, що приймають суперклас. Я не розумію вашого значення?

1
Я видалю зауваження LSP (це не додає значення відповіді).
iwein

Так, успадкування відстій, і дурний фреймворк, з яким я застряг, розроблений з успадкуванням як єдиним варіантом.
Шрідхар Сарнобат

Припускаючи, що ви не можете перепроектувати суперклас, ви можете витягти //some codesкод у метод, який можна перевірити окремо.
фазмальний

1
Добре, зрозумів. Це інша проблема, ніж те, що я намагався вирішити, коли шукав це, але принаймні це непорозуміння вирішило мою власну проблему, щоб знущатися над дзвінками з базового класу (що я насправді не замінюю).
Гійом Перро

86

Якщо у вас дійсно немає вибору для рефакторингу, ви можете знущатись / заглушати все у виклику супер методу, наприклад

    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

фантастичне рішення творить для мене чудеса, коли я хочу повернути знущане значення із методу суперкласу для використання дитиною, фантастичне спасибі.
Гурнард

14
це працює добре, якщо перевірка не прихована або метод збереження не працює безпосередньо замість того, щоб викликати інший метод. mockito не: Mockito.doNothing (). when ((BaseService) шпигун) .save (); це не буде робити нічого на базовій службі, крім збереження дочірньої служби :(
tibi

Я не можу змусити це працювати на моєму - це також пригнічує метод дитини. BaseServiceє абстрактним, хоча я не розумію, чому це було б доречно.
Шрідхар Сарнобат

1
@ Срідхар-Сарнобат, так, я бачу те саме :( хтось знає, як змусити його лише super.validate()
заглушити

4

Подумайте про переробку коду з методу ChildService.save () на інший метод і протестуйте цей новий метод замість тестування ChildService.save (), таким чином ви уникнете непотрібного виклику методу super.

Приклад:

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

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

Можливо, найпростіший варіант, якщо спадкування має сенс - це створити новий метод (пакунок private ??) для виклику супер (дозволимо назвати його superFindall), шпигувати реальний екземпляр, а потім знущатися над методом superFindAll () так, як ви хотіли знущатись батьківський клас. Це не ідеальне рішення з точки зору охоплення та видимості, але воно повинно виконувати роботу, і його легко застосувати.

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

    void superSave(){
        super.save();
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.