Имитация конструктора с параметром


89

У меня есть класс, как показано ниже:

public class A {
    public A(String test) {
        bla bla bla
    }

    public String check() {
        bla bla bla
    }
}

Логика в конструкторе A(String test)и check()то, что я пытаюсь высмеять. Я хочу звонки вроде:new A($$$any string$$$).check() возвращает фиктивную строку "test".

Я старался:

 A a = mock(A.class); 
 when(a.check()).thenReturn("test");

 String test = a.check(); // to this point, everything works. test shows as "tests"

 whenNew(A.class).withArguments(Matchers.anyString()).thenReturn(rk);
 // also tried:
 //whenNew(A.class).withParameterTypes(String.class).withArguments(Matchers.anyString()).thenReturn(rk);

 new A("random string").check();  // this doesn't work

Но похоже, что это не работает. new A($$$any string$$$).check()по-прежнему выполняет логику конструктора вместо получения фиктивного объекта A.


ваш фиктивный метод check () работает правильно?
Ben Glasser

@BenGlasser check () работает нормально. Просто whenNew, похоже, вообще не работает. Я тоже обновил описание.
Shengjie

Ответы:


93

Код, который вы разместили, работает для меня с последней версией Mockito и Powermockito. Может ты не подготовил А? Попробуй это:

A.java

public class A {
     private final String test;

    public A(String test) {
        this.test = test;
    }

    public String check() {
        return "checked " + this.test;
    }
}

MockA.java

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.Test;
import org.junit.runner.RunWith;
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(A.class)
public class MockA {
    @Test
    public void test_not_mocked() throws Throwable {
        assertThat(new A("random string").check(), equalTo("checked random string"));
    }
    @Test
    public void test_mocked() throws Throwable {
         A a = mock(A.class); 
         when(a.check()).thenReturn("test");
         PowerMockito.whenNew(A.class).withArguments(Mockito.anyString()).thenReturn(a);
         assertThat(new A("random string").check(), equalTo("test"));
    }
}

Оба теста должны пройти с mockito 1.9.0, powermockito 1.4.12 и junit 4.8.2.


24
Также обратите внимание, что если конструктор вызывается из другого класса, включите его в список вPrepareForTest
Jeff E

У кого-нибудь есть идея, почему мы должны готовить себя при вызове PowerMockito.whenNew?
Удаянга 05

50

Насколько мне известно, вы не можете имитировать конструкторы с помощью mockito, только методы. Но согласно вики на кодовой странице Mockito google, есть способ имитировать поведение конструктора, создав метод в вашем классе, который возвращает новый экземпляр этого класса. тогда вы можете издеваться над этим методом. Ниже приведен отрывок непосредственно из вики Mockito :

Паттерн 1 - использование однострочных методов для создания объекта

Чтобы использовать шаблон 1 (тестирование класса MyClass), вы должны заменить вызов вида

   Foo foo = new Foo( a, b, c );

с участием

   Foo foo = makeFoo( a, b, c );

и напишите однострочный метод

   Foo makeFoo( A a, B b, C c ) { 
        return new Foo( a, b, c );
   }

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

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

Ваш тестовый класс может содержать такие члены, как

  @Mock private Foo mockFoo;
  private MyClass toTest = spy(new MyClass());

Наконец, внутри вашего тестового метода вы имитируете вызов makeFoo с помощью строки вроде

  doReturn( mockFoo )
      .when( toTest )
      .makeFoo( any( A.class ), any( B.class ), any( C.class ));

Вы можете использовать сопоставители, которые более специфичны, чем any (), если хотите проверить аргументы, передаваемые конструктору.

Если вы просто хотите вернуть фиктивный объект своего класса, я думаю, это должно сработать для вас. В любом случае вы можете узнать больше о создании mocking-объекта здесь:

http://code.google.com/p/mockito/wiki/MockingObjectCreation


21
+1, мне не нравится, что мне нужно корректировать исходный код, чтобы сделать его более дружественным к mockito. Спасибо за то, что поделился.
Shengjie

22
Никогда не плохо иметь исходный код, который можно тестировать, или избегать анти-шаблонов тестируемости при написании кода. Если вы напишете источник, который более тестируемый, он автоматически станет более удобным в сопровождении. Изоляция вызовов конструкторов в их собственных методах - лишь один из способов добиться этого.
Давуд ибн Карим

1
Написание тестируемого кода - это хорошо. Меня заставляют перепроектировать класс A, чтобы я мог писать тесты для класса B, который зависит от A, потому что A имеет жестко заданную зависимость от C, и я чувствую себя ... менее хорошо. Да, в конце концов код будет лучше, но сколько классов я в конечном итоге переделываю, чтобы закончить написание одного теста?
Марк Вуд,

@MarkWood по моему опыту неуклюжие опыты тестирования обычно являются признаком некоторого недостатка дизайна. IRL, если вы тестируете конструкторы, ваш код, вероятно, кричит вам о фабрике или какой-либо инъекции зависимостей. Если вы будете следовать типичным шаблонам проектирования для этих двух случаев, ваш код станет намного проще тестировать и работать с ним в целом. Если вы тестируете конструкторы, потому что у вас есть куча логики, вам, вероятно, понадобится некоторый уровень полиморфизма, или вы можете переместить эту логику в метод инициализации.
Бен Глассер,

12

Без использования Powermock ... См. Пример ниже, основанный на ответе Бена Глассера, так как мне потребовалось некоторое время, чтобы понять это ... надеюсь, что это сэкономит несколько раз ...

Исходный класс:

public class AClazz {

    public void updateObject(CClazz cClazzObj) {
        log.debug("Bundler set.");
        cClazzObj.setBundler(new BClazz(cClazzObj, 10));
    } 
}

Модифицированный класс:

@Slf4j
public class AClazz {

    public void updateObject(CClazz cClazzObj) {
        log.debug("Bundler set.");
        cClazzObj.setBundler(getBObject(cClazzObj, 10));
    }

    protected BClazz getBObject(CClazz cClazzObj, int i) {
        return new BClazz(cClazzObj, 10);
    }
 }

Тестовый класс

public class AClazzTest {

    @InjectMocks
    @Spy
    private AClazz aClazzObj;

    @Mock
    private CClazz cClazzObj;

    @Mock
    private BClazz bClassObj;

    @Before
    public void setUp() throws Exception {
        Mockito.doReturn(bClassObj)
               .when(aClazzObj)
               .getBObject(Mockito.eq(cClazzObj), Mockito.anyInt());
    }

    @Test
    public void testConfigStrategy() {
        aClazzObj.updateObject(cClazzObj);

        Mockito.verify(cClazzObj, Mockito.times(1)).setBundler(bClassObj);
    }
}

6

С mockito вы можете использовать withSettings (), например, если CounterService требовал 2 зависимости, вы можете передать их как имитацию:

UserService userService = Mockito.mock(UserService.class); SearchService searchService = Mockito.mock(SearchService.class); CounterService counterService = Mockito.mock(CounterService.class, withSettings().useConstructor(userService, searchService));


На мой взгляд, самый простой и лучший ответ. Спасибо.
Eldon

4

Mockito имеет ограничения по тестированию final, статических и частных методов.

с библиотекой тестирования jMockit вы можете сделать несколько вещей очень простыми и понятными, как показано ниже:

Мок-конструктор класса java.io.File:

new MockUp<File>(){
    @Mock
    public void $init(String pathname){
        System.out.println(pathname);
        // or do whatever you want
    }
};
  • имя общедоступного конструктора следует заменить на $ init
  • выброшенные аргументы и исключения остаются такими же
  • тип возврата должен быть определен как void

Смоделируйте статический метод:

  • удалить статику из фиктивной сигнатуры метода
  • сигнатура метода остается прежней
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.