Знущаються над конструктором з параметром


90

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

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 () працює нормально. Тільки тоді, колиNew, здається, не працює взагалі. Я також оновив опис.
Shengjie

Відповіді:


93

Опублікований вами код працює для мене з останньою версією Mockito та Powermockito. Може, ви не підготували А? Спробуйте це:

А.ява

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


25
Також зверніть увагу, що якщо конструктор викликається з іншого класу, включіть його до списку вPrepareForTest
Jeff E

У когось є ідея, чому ми повинні готувати себе, коли викликається "PowerMockito.whenNew"?
udayanga

50

Наскільки мені відомо, ви не можете знущатися над конструкторами з mockito, лише з методами. Але згідно з вікі на кодовій сторінці Google Mockito, є спосіб знущатися над поведінкою конструктора, створюючи у своєму класі метод, який повертає новий екземпляр цього класу. тоді ви можете висміяти цей метод. Нижче наведено уривок безпосередньо з вікі 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 ));

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

Якщо ви просто хочете повернути знущаний об’єкт вашого класу, я думаю, це має працювати для вас. У будь-якому випадку ви можете прочитати більше про знущання над створенням об'єкта тут:

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


21
+1, мені не подобається той факт, що мені потрібно налаштувати свій вихідний код, щоб зробити його більш мокетним. Дякуємо за обмін.
Shengjie

22
Ніколи не погано мати вихідний код, який більш перевіряється, або уникати анти-шаблонів перевіреності, коли ви пишете свій код. Якщо ви пишете джерело, яке більш перевіряється, воно автоматично більш ремонтопридатне. Виділення викликів конструктора за їх власними методами - це лише один із способів досягти цього.
Dawood ibn Kareem

1
Написати перевіряється код - це добре. Змушений переробити клас А, щоб я міг писати тести для класу В, який залежить від А, оскільки А має жорстко закодовану залежність від С, відчуває себе ... менш добре. Так, зрештою код буде кращим, але скільки класів я в підсумку перероблю, щоб я міг закінчити написання одного тесту?
Марк Вуд

На моєму досвіді @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));


На мій погляд, найпростіша і найкраща відповідь. Дякую.
Елдон,

4

Mockito має обмеження тестування остаточного, статичного та приватного методів.

за допомогою тестової бібліотеки 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.