Как протестировать контроллер с внедрением конструктора с помощью MockMvc

У меня есть контроллер с внедрением конструктора

@RestController
@RequestMapping("/user")
public class MainController {

    private final UserMapper userMapper; // autowired by constructor below

    public MainController(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @RequestMapping("/getChannels")
    public String index() {
        LoginUser user = userMapper.getUserByName("admin");
        return "Channels: " + user.getChannels();
    }
}

Это простой класс, который отлично работает. Однако, когда я попытался запустить тестирование JUnit с классом ниже, я получил ошибку.

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class MainControllerTest {

    private MockMvc mvc;
    private final UserMapper userMapper;

    public MainControllerTest(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Before
    public void setUp() throws Exception {
        mvc = MockMvcBuilders.standaloneSetup(new MainController(userMapper)).build();
    }

    ......

Ошибка:

java.lang.Exception: Test class should have exactly one public zero-argument constructor

Меня смутило приведенное выше сообщение об ошибке: как я могу внедрить userMapper с конструктором без аргументов? Я знаю, что можно добавить @Autowired для userMapper в MainController, однако внедрение поля не рекомендуется. Пожалуйста, может ли кто-нибудь указать мне подходящий способ как для внедрения конструктора, так и для тестирования MockMvc. Спасибо.


person Daniel    schedule 19.03.2017    source источник


Ответы (3)


Другие ответы говорят об использовании аннотаций, но здесь ваша проблема не имеет никакого отношения к использованию аннотаций. имейте в виду, что в Spring 4.3+ вам не нужно аннотировать конструктор для зависимостей, подробнее здесь.

На самом деле вам не нужно пытаться имитировать внедрение конструктора в свой тестовый класс (MainControllerTest). Все, что вам нужно, это объявить UserMapper как компонент Spring в контексте вашего приложения, и в вашем тестовом классе он будет автоматически введен в ваш контроллер как ваше работающее приложение.

Что означает ваша ошибка: все классы Junit Test, как сообщается в сообщении об ошибке, должны иметь ровно один общедоступный конструктор с нулевым аргументом, потому что наборы тестов Junit в таких случаях, как ваш сценарий, не знают, как создать экземпляр тестового класса.

person Hamid Samani    schedule 19.03.2017
comment
У меня есть другое сомнение здесь. Есть ли способ получить экземпляр MainController в тестовом классе без использования ключевого слова «новое» или аннотации @Autowired? - person das; 26.02.2020

Используйте @Autowire на поле. Внедрение полей не рекомендуется в производственном коде, потому что это запутывает рассуждения (что было введено, куда и почему). Но тестовые контексты (и особенно такие, как этот) просты, так что здесь нет ловушки жестких рассуждений.

@Autowired
private final UserMapper userMapper;

public MainControllerTest() { //remove me, implicit
}

Это изменение устранит проблему, так как jUnit будет иметь конструктор, который он понимает, и сможет создать экземпляр тестового класса.

person malejpavouk    schedule 19.03.2017

Вам не хватает @Autowired для конструктора MainController, и для решения проблемы всегда используйте внедрение конструктора, как показано ниже:

@RestController
@RequestMapping("/user")
public class MainController {

    private final UserMapper userMapper;

    @Autowired
    public MainController(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    //add methods here
}

Использовать внедрение поля НЕ рекомендуется, см. здесь для более подробной информации.

Кроме того, чтобы решить эту проблему, вам необходимо обновить свой тестовый класс, как показано ниже:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class MainControllerTest {

    private MockMvc mvc;

    @Mock
    private UserMapper userMapper;

    @InjectMocks
    private MainController mainController = new MainController(userMapper);

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mvc = MockMvcBuilders.standaloneSetup(mainController)..build();
    }

    ......
}
person developer    schedule 19.03.2017
comment
Я протестировал предложенный вами исходный код. Затем я получил NPE в MainController. Это было вызвано тем, что userMapper, сгенерированный @InjectMocks, не работает. Во всяком случае, я нашел решение заменить аннотацию WebAppConfiguration на SpringBootTest. Однако я все же хотел бы знать, как сделать его работоспособным без Spring Boot. - person Daniel; 19.03.2017
comment
Ставьте минус, так как код не работает. NPE неизбежен при инициализации поля mainController в тесте. Поле не следует назначать вручную, так как поле userMapper в этом месте по-прежнему равно null. Он инициализируется позже в методе setUp() с помощью initMocks(this). Таким образом, общее правило заключается в том, чтобы не назначать поле вручную, если в поле присутствует @InjectMocks. Mockito несет ответственность за внедрение всех фиктивных зависимостей и предоставление экземпляра объекта. Не говоря уже о tedvinke.wordpress.com/2014/02/13/ - person Marcin Kłopotek; 10.04.2018
comment
Марчин прав. Особенно, когда вы передаете зависимости через конструктор, приведенный выше код не работает. Вам нужно перейти по ссылке, которую дал Марчин. 1. RunWith (MockitoJUnitRunner.class) 2. Удалите InjectMocks из контроллера. На самом деле никаких аннотаций для контроллера не требуется. 3. Инициализируйте контроллер в функции с пометкой «До». - person Aykut Akıncı; 30.01.2019