Mock konstruktora z parametrem

89

Mam klasę jak poniżej:

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

    public String check() {
        bla bla bla
    }
}

Logika w konstruktorze A(String test)i check()rzeczy, które próbuję kpić. Chcę wywołania typu: new A($$$any string$$$).check()zwraca fikcyjny ciąg "test".

Próbowałem:

 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

Ale to nie wydaje się działać. new A($$$any string$$$).check()nadal przechodzi przez logikę konstruktora zamiast pobierać mockowany obiekt A.

Shengjie
źródło
czy twoja udawana metoda check () działa poprawnie?
Ben Glasser
@BenGlasser check () działa poprawnie. Po prostu kiedyNowe w ogóle nie działa. Zaktualizowałem również opis.
Shengjie

Odpowiedzi:

93

Kod, który opublikowałeś, działa dla mnie z najnowszą wersją Mockito i Powermockito. Może nie przygotowałeś A? Spróbuj tego:

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"));
    }
}

Oba testy powinny przejść z mockito 1.9.0, powermockito 1.4.12 i junit 4.8.2

Alban
źródło
24
Zwróć też uwagę, że jeśli konstruktor jest wywoływany z innej klasy, umieść go na liście wPrepareForTest
Jeff E
Czy ktoś ma pomysł, dlaczego powinniśmy przygotowywać siebie, gdy wywoływane jest „PowerMockito.whenNew”?
udayanga
50

O ile wiem, nie można kpić z konstruktorów za pomocą mockito, tylko metody. Ale zgodnie z wiki na stronie kodowej Google Mockito istnieje sposób na oszukanie zachowania konstruktora poprzez utworzenie metody w Twojej klasie, która zwraca nową instancję tej klasy. wtedy możesz kpić z tej metody. Poniżej znajduje się fragment bezpośrednio z wiki Mockito :

Wzorzec 1 - wykorzystanie metod jednowierszowych do tworzenia obiektów

Aby użyć wzorca 1 (testowanie klasy o nazwie MyClass), należy zamienić wywołanie typu

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

z

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

i napisz metodę jednowierszową

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

Ważne jest, aby metoda nie zawierała żadnej logiki; tylko jedna linia, która tworzy obiekt. Powodem tego jest to, że sama metoda nigdy nie będzie testowana jednostkowo.

Kiedy przyjdziesz, aby przetestować klasę, testowany obiekt będzie w rzeczywistości szpiegiem Mockito, z zastąpioną tą metodą, aby zwrócić próbę. Dlatego testujesz nie samą klasę, ale jej nieznacznie zmodyfikowaną wersję.

Twoja klasa testowa może zawierać członków, takich jak

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

Na koniec w swojej metodzie testowej wyśmiewasz wywołanie makeFoo za pomocą linii takiej jak

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

Możesz użyć dopasowań, które są bardziej szczegółowe niż jakiekolwiek (), jeśli chcesz sprawdzić argumenty przekazywane do konstruktora.

Jeśli chcesz tylko zwrócić wyszydzony przedmiot ze swojej klasy, myślę, że to powinno działać dla Ciebie. W każdym razie możesz przeczytać więcej o mockowaniu tworzenia obiektów tutaj:

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

Ben Glasser
źródło
21
+1, nie podoba mi się fakt, że muszę dostosować mój kod źródłowy, aby był bardziej przyjazny dla mockito. Dzięki za udostępnienie.
Shengjie
22
Nigdy nie jest źle mieć kod źródłowy, który jest bardziej testowalny, lub unikać anty-wzorców testowalności podczas pisania kodu. Jeśli napiszesz źródło, które jest bardziej testowalne, automatycznie jest łatwiejsze w utrzymaniu. Izolowanie wywołań konstruktora w ich własnych metodach to tylko jeden ze sposobów osiągnięcia tego.
Dawood ibn Kareem
1
Pisanie testowalnego kodu jest dobre. Zmuszenie do przeprojektowania klasy A, abym mógł pisać testy dla klasy B, która zależy od A, ponieważ A ma zakodowaną na stałe zależność od C, wydaje się ... gorzej. Tak, w końcu kod będzie lepszy, ale ile klas ostatecznie przeprojektuję, aby móc napisać jeden test?
Mark Wood
@MarkWood z mojego doświadczenia wynika, że ​​niezgrabne testy są zazwyczaj oznaką pewnej wady projektu. IRL, jeśli testujesz konstruktory, twój kod prawdopodobnie wrzeszczy na ciebie o fabrykę lub wstrzyknięcie zależności. Jeśli będziesz postępować zgodnie z typowymi wzorcami projektowymi dla tych dwóch przypadków, kod będzie znacznie łatwiejszy do testowania i ogólnie do pracy. Jeśli testujesz konstruktory, ponieważ masz tam mnóstwo logiki, prawdopodobnie potrzebujesz jakiejś warstwy polimorfizmu lub możesz przenieść tę logikę do metody inicjalizacji.
Ben Glasser
12

Bez korzystania z Powermock .... Zobacz poniższy przykład oparty na odpowiedzi Bena Glassera, ponieważ zajęło mi to trochę czasu ... mam nadzieję, że zaoszczędzi to trochę czasu ...

Oryginalna klasa:

public class AClazz {

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

Zmodyfikowana klasa:

@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);
    }
 }

Klasa testowa

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);
    }
}
user666
źródło
6

Z mockito możesz użyć withSettings (), na przykład jeśli CounterService wymagał 2 zależności, możesz przekazać je jako makietę:

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

MevlütÖzdemir
źródło
Moim zdaniem najłatwiejsza i najlepsza odpowiedź. Dziękuję Ci.
Eldon
4

Mockito ma ograniczenia testowania metod ostatecznych, statycznych i prywatnych.

dzięki bibliotece testowej jMockit możesz zrobić kilka rzeczy bardzo łatwych i prostych, jak poniżej:

Mock konstruktor klasy java.io.File:

new MockUp<File>(){
    @Mock
    public void $init(String pathname){
        System.out.println(pathname);
        // or do whatever you want
    }
};
  • publiczną nazwę konstruktora należy zastąpić $ init
  • zgłaszane argumenty i wyjątki pozostają takie same
  • zwracany typ powinien być zdefiniowany jako void

Mock metodę statyczną:

  • usuń statyczny z makiety podpisu metody
  • w przeciwnym razie podpis metody pozostaje taki sam
Amit Kaneria
źródło