Инъекция @Полученное личное поле во время тестирования
У меня есть настройка компонента, которая по сути является лаунчером для приложения. Это настроено так:
@Component
public class MyLauncher {
@Autowired
MyService myService;
//other methods
}
MyService помечен аннотацией @Service
Spring и без каких-либо проблем автоматически подключается к моему классу запуска.
Я хотел бы написать несколько тестовых примеров jUnit для MyLauncher, для этого я запустил такой класс:
public class MyLauncherTest
private MyLauncher myLauncher = new MyLauncher();
@Test
public void someTest() {
}
}
Могу ли я создать объект Mock для MyService и внедрить его в myLauncher в моем тестовом классе? В настоящее время у меня нет getter или setter в myLauncher, так как Spring обрабатывает автоматическое подключение. Если возможно, я бы не хотел добавлять геттеры и сеттеры. Могу ли я сказать контрольному коду ввести фиктивный объект в переменную autowired, используя @Before
инициализации @Before
?
Если я говорю об этом совершенно неправильно, не стесняйтесь говорить это. Я все еще новичок в этом. Моя основная цель - просто иметь некоторый Java-код или аннотацию, которая помещает фиктивный объект в эту переменную @Autowired
без необходимости писать метод установки или использовать файл applicationContext-test.xml
. Я бы предпочел сохранить все для тестовых случаев в файле .java
вместо того, чтобы поддерживать отдельный контент приложения только для моих тестов.
Я надеюсь использовать Mockito для макетов объектов. В прошлом я делал это с помощью org.mockito.Mockito
и создавал свои объекты с помощью Mockito.mock(MyClass.class)
.
Ответы
Ответ 1
В своем тесте вы можете абсолютно инцетировать mocks на MyLauncher. Я уверен, что если вы покажете, какую издевательскую структуру вы используете, кто-то быстро даст ответ. С mockito я бы рассмотрел использование @RunWith (MockitoJUnitRunner.class) и использование аннотаций для myLauncher. Он будет выглядеть примерно так, как показано ниже.
@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
@InjectMocks
private MyLauncher myLauncher = new MyLauncher();
@Mock
private MyService myService;
@Test
public void someTest() {
}
}
Ответ 2
Принятый ответ (используйте MockitoJUnitRunner
и @InjectMocks
) отлично. Но если вы хотите что-то немного более легкое (без специального бегуна JUnit) и менее "волшебное" (более прозрачное), особенно для случайного использования, вы можете просто установить частные поля непосредственно с помощью интроспекции.
Если вы используете Spring, у вас уже есть класс утилиты для этого: org.springframework.test.util.ReflectionTestUtils
Использование довольно просто:
ReflectionTestUtils.setField(myLauncher, "myService", myService);
Первым аргументом является ваша цель bean, вторая - имя (обычно закрытого) поля, а последнее - значение для ввода.
Если вы не используете Spring, достаточно реализовать такой метод утилиты. Вот код, который я использовал до того, как нашел этот класс 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);
}
}
Ответ 3
Иногда вы можете реорганизовать свой @Component
, чтобы использовать инсталляцию на основе конструктора или сеттера, чтобы настроить ваш тестовый файл (вы можете и по-прежнему полагаться на @Autowired
). Теперь вы можете полностью создать свой тест без издевательской структуры, заменив тестовые заглушки (например, Martin Fowler MailServiceStub):
@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() {
}
}
Этот метод особенно полезен, если тест и тестируемый класс находятся в одном пакете, потому что тогда вы можете использовать значение по умолчанию, package-private, чтобы другие классы не обращались к нему. Обратите внимание, что вы все еще можете иметь свой производственный код в src/main/java
, но ваши тесты в каталогах src/main/test
.
Если вам нравится Mockito, тогда вы оцените MockitoJUnitRunner. Это позволяет вам делать "магические" вещи, такие как @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() {
}
}
В качестве альтернативы вы можете использовать бегун JUnit по умолчанию и вызвать MockitoAnnotations.initMocks() в методе setUp()
, чтобы позволить Mockito инициализировать аннотированные значения. Вы можете найти дополнительную информацию в javadoc @InitMocks и в блоге, который я написал.
Ответ 4
Посмотрите на ссылку
Затем напишите свой тестовый пример как
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml"})
public class MyLauncherTest{
@Resource
private MyLauncher myLauncher ;
@Test
public void someTest() {
//test code
}
}
Ответ 5
Я полагаю, что для автоматической работы над классом MyLauncher (для myService) вам нужно разрешить Spring инициализировать его вместо вызова конструктора с помощью автоматической разметки myLauncher. Как только это происходит автоматически (и myService также получает автоматическое подключение), Spring (версии 1.4.0 и выше) предоставляет аннотацию @MockBean, которую вы можете вставить в свой тест. Это заменит совпадающие отдельные компоненты в контексте на макет этого типа. Затем вы можете дополнительно определить, что вы хотите, в методе @Before.
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();
}
}
Ваш класс MyLauncher может затем остаться неизменным, и ваш компонент MyService будет имитировать, методы которого возвращают значения, как вы определили:
@Component
public class MyLauncher {
@Autowired
MyService myService;
public void doSomething() {
myService.someVoidMethod();
myService.someMethodThatCallsSomeStringMethod();
}
//other methods
}
Пара преимуществ этого перед другими упомянутыми методами состоит в том, что:
- Вам не нужно вручную вводить myService.
- Вам не нужно использовать бегун Mockito или правила.
Ответ 6
Я новый пользователь для Spring. Я нашел другое решение для этого. Использование отражения и создание общедоступных полей и назначение макетных объектов.
Это мой auth-контроллер, и у него есть некоторые личные свойства Autwired.
@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;
}
}
И это мой тест Junit для AuthController. Я делаю публичные необходимые частные свойства и присваиваю им макетные объекты и рок:)
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);
}
}
}
Я не знаю преимуществ и недостатков таким образом, но это работает. У этой техники есть немного больше кода, но эти коды могут быть разделены различными методами и т.д. Есть более хорошие ответы на этот вопрос, но я хочу указать на другое решение. Извините за мой плохой английский. Имейте хороший java для всех:)
Ответ 7
Я пытался то же самое и использовал ReflectionUtils, чтобы обернуть RESTWebservice
ReflectionTestUtils.setField(dao,"myRESTService",myRESTService);
Теперь в дао, если я пытаюсь вызвать Webservice, он не работает. Объект не является нулевым, но вызов не выполняется. Есть ли какие-либо ограничения на REST WEB-сервис?