Injecting @ Autowired private field during testing

Mam konfigurację komponentu, który jest zasadniczo Launcherem dla aplikacji. Jest skonfigurowany tak:

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    //other methods
}

MyService jest adnotowany za pomocą adnotacji Spring @Service i jest automatycznie przypisywany do mojej klasy launchera bez żadnych problemów.

Chciałbym napisać kilka testów jUnit dla MyLauncher, w tym celu założyłem taką klasę:

public class MyLauncherTest
    private MyLauncher myLauncher = new MyLauncher();

    @Test
    public void someTest() {

    }
}

Czy mogę utworzyć Mock obiekt dla MyService i wstrzyknąć go do myLauncher w mojej klasie testowej? Obecnie nie mam gettera lub seter w myLauncher jako sprężyna zajmuje się autowirowaniem. Jeśli to możliwe, chciałbym nie dodawać getterów i seterów. Czy Mogę polecić testowemu przypadku wstrzyknięcie wzorcowego obiektu do zmiennej autowired przy użyciu metody @Before init?

Jeśli źle do tego podchodzę, możesz to powiedzieć. Wciąż jestem w tym nowy. Moim głównym celem jest posiadanie kodu Javy lub adnotacji, która umieszcza obiekt w zmiennej @Autowired bez konieczności pisania metody settera lub używania applicationContext-test.plik xml. Wolałbym zachować wszystko dla przypadków testowych w .plik java zamiast konieczności utrzymywania osobnego applicationContent tylko dla moich testów.

Mam nadzieję użyć Mockito dla mock obiektów. W przeszłości robiłem to używając org.mockito.Mockito i tworząc moje obiekty za pomocą Mockito.mock(MyClass.class)

Wielkie dzięki.
Author: Pierre Henry, 2013-05-07

6 answers

Możesz bezwzględnie wstrzyknąć mocks na MyLauncher w teście. Jestem pewien, że jeśli pokażesz, z jakiego frameworka korzystasz, ktoś szybko dostarczy odpowiedź. Z mockito chciałbym przyjrzeć się użyciu @ RunWith (MockitoJUnitRunner.klasa) i używanie adnotacji do myLauncher. Wyglądałoby to jak to, co jest poniżej.

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher = new MyLauncher();

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}
 57
Author: Manuel Quinones,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-07 18:55:43

Przyjęta odpowiedź (użyj MockitoJUnitRunner i @InjectMocks) jest świetna. Ale jeśli chcesz coś nieco lżejszego (bez specjalnego biegacza JUnit) i mniej "magicznego" (bardziej przezroczystego), szczególnie do okazjonalnego użytku, możesz po prostu ustawić prywatne pola bezpośrednio za pomocą introspekcji.

Jeśli używasz sprężyny, masz już klasę użyteczności do tego : org.springframework.test.util.ReflectionTestUtils

Użycie jest dość proste:

ReflectionTestUtils.setField(myLauncher, "myService", myService);

Pierwszy argument to twój cel, drugi to nazwa pole (Zwykle prywatne), a ostatnia jest wartością do wstrzyknięcia.

Jeśli nie używasz Springa, wdrożenie takiej metody użytkowej jest dość trywialne. Oto kod, którego użyłem, zanim znalazłem tę klasę Spring:

public static void setPrivateField(Object target, String fieldName, Object value){
        try{
            Field privateField = target.getClass().getDeclaredField(fieldName);
            privateField.setAccessible(true);
            privateField.set(target, value);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
 19
Author: Pierre Henry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-16 08:17:04

Czasami możesz refaktorować @Component, aby użyć konstruktora lub settera do Ustawienia skrzyni testowej(możesz i nadal możesz polegać na @Autowired). Teraz możesz utworzyć swój test całkowicie bez szyderczego frameworka, implementując zamiast niego stuby testowe (np. MailServiceStub Martina Fowlera):

@Component
public class MyLauncher {

    private MyService myService;

    @Autowired
    MyLauncher(MyService myService) {
        this.myService = myService;
    }

    // other methods
}

public class MyServiceStub implements MyService {
    // ...
}

public class MyLauncherTest
    private MyLauncher myLauncher;
    private MyServiceStub myServiceStub;

    @Before
    public void setUp() {
        myServiceStub = new MyServiceStub();
        myLauncher = new MyLauncher(myServiceStub);
    }

    @Test
    public void someTest() {

    }
}

Ta technika jest szczególnie przydatna, jeśli test i testowana Klasa znajdują się w tym samym pakiecie, ponieważ wtedy można użyć domyślnego, package-private access modyfikator uniemożliwiający dostęp do niego innym klasom. Zauważ, że nadal możesz mieć swój kod produkcyjny w src/main/java, ale testy w katalogach src/main/test.


Jeśli lubisz Mockito, docenisz MockitoJUnitRunner. Pozwala robić "magiczne" rzeczy, jak pokazywał ci @ Manuel:
@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher; // no need to call the constructor

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}

Alternatywnie, możesz użyć domyślnego runnera JUnit i wywołać MockitoAnnotations.initMocks () w metodzie setUp() pozwalającej Mockito inicjalizować adnotację wartości. Więcej informacji można znaleźć w javadoc @initmocks oraz w blogu , który napisałem.

 13
Author: matsev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-07 19:30:47

Spójrz na ten link

Następnie napisz swój przypadek testowy jako

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml"})
public class MyLauncherTest{

@Resource
private MyLauncher myLauncher ;

   @Test
   public void someTest() {
       //test code
   }
}
 1
Author: NullPointerException,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-07 18:55:18

Jestem nowym użytkownikiem na wiosnę. Znalazłem na to inne rozwiązanie. Korzystanie z odbicia i upublicznianie niezbędnych pól i przypisywanie obiektów makiety.

To jest mój kontroler auth i ma pewne Autowired prywatne właściwości.

@RestController
public class AuthController {

    @Autowired
    private UsersDAOInterface usersDao;

    @Autowired
    private TokensDAOInterface tokensDao;

    @RequestMapping(path = "/auth/getToken", method = RequestMethod.POST)
    public @ResponseBody Object getToken(@RequestParam String username,
            @RequestParam String password) {
        User user = usersDao.getLoginUser(username, password);

        if (user == null)
            return new ErrorResult("Kullanıcıadı veya şifre hatalı");

        Token token = new Token();
        token.setTokenId("aergaerg");
        token.setUserId(1);
        token.setInsertDatetime(new Date());
        return token;
    }
}

A to jest mój test Junit dla AuthController. Robię publiczne potrzebne prywatne nieruchomości i przypisuję im makiety obiektów i rock :)

public class AuthControllerTest {

    @Test
    public void getToken() {
        try {
            UsersDAO mockUsersDao = mock(UsersDAO.class);
            TokensDAO mockTokensDao = mock(TokensDAO.class);

            User dummyUser = new User();
            dummyUser.setId(10);
            dummyUser.setUsername("nixarsoft");
            dummyUser.setTopId(0);

            when(mockUsersDao.getLoginUser(Matchers.anyString(), Matchers.anyString())) //
                    .thenReturn(dummyUser);

            AuthController ctrl = new AuthController();

            Field usersDaoField = ctrl.getClass().getDeclaredField("usersDao");
            usersDaoField.setAccessible(true);
            usersDaoField.set(ctrl, mockUsersDao);

            Field tokensDaoField = ctrl.getClass().getDeclaredField("tokensDao");
            tokensDaoField.setAccessible(true);
            tokensDaoField.set(ctrl, mockTokensDao);

            Token t = (Token) ctrl.getToken("test", "aergaeg");

            Assert.assertNotNull(t);

        } catch (Exception ex) {
            System.out.println(ex);
        }
    }

}

Nie znam zalet i wad tego sposobu, ale to działa. Ten technic ma trochę więcej kodu, ale te kody mogą być oddzielone różnymi metodami itp. Jest więcej dobrych odpowiedzi na to pytanie, ale chcę wskazać inne rozwiązanie. Przepraszam za zły angielski. Życzę wszystkim dobrej Javy:)

 1
Author: kodmanyagha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-06 07:44:15

Wierzę, że aby auto-okablowanie działało na twojej klasie MyLauncher (dla myService), musisz pozwolić Spring zainicjalizować ją zamiast wywoływać konstruktor, przez automatyczne okablowanie myLauncher. Gdy jest to auto-wired (i myService jest również auto-wired), Spring (1.4.0 i nowsze) zapewnia adnotację @ MockBean, którą możesz umieścić w swoim teście. To zastąpi pasujące pojedyncze fasolki w kontekście makietą tego typu. Możesz następnie określić, co chcesz wyśmiewać, w @Before metoda.

public class MyLauncherTest
    @MockBean
    private MyService myService;

    @Autowired
    private MyLauncher myLauncher;

    @Before
    private void setupMockBean() {
        doNothing().when(myService).someVoidMethod();
        doReturn("Some Value").when(myService).someStringMethod();
    }

    @Test
    public void someTest() {
        myLauncher.doSomething();
    }
}

Twoja klasa MyLauncher może pozostać niezmodyfikowana, a Twoja fasola MyService będzie makietą, której metody zwracają wartości zdefiniowane przez Ciebie:

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    public void doSomething() {
        myService.someVoidMethod();
        myService.someMethodThatCallsSomeStringMethod();
    }

    //other methods
}

Kilka zalet tego w porównaniu z innymi wymienionymi metodami jest to, że:

  1. nie musisz ręcznie wstrzykiwać myService.
  2. Nie musisz używać Mockito runner ani zasad.
 0
Author: snydergd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-27 14:14:08