Как написать unit test случай для класса контроллера с использованием mockito

Я очень новичок в Mockito и jUnit, и я стараюсь изучить правильный способ сделать TDD. Мне нужны пары примеров, чтобы я мог написать unit test с помощью mockito

Ниже приведен мой класс контроллера, который загружает файл и выполняет некоторые действия на входе этого файла.

@Controller
@RequestMapping("/registration")
public class RegistrationController {

    @Autowired
    private RegistrationService RegistrationService;

    @Value("#{Properties['uploadfile.location']}")
    private String uploadFileLocation;

    public RegistrationController() {

    }

    @RequestMapping(method = RequestMethod.GET)
    public String getUploadForm(Model model) {
        model.addAttribute(new Registration());
        return "is/Registration";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String create(Registration registration, BindingResult result,ModelMap model)
            throws NumberFormatException, Exception {

        File uploadedFile = uploadFile(registration);
        List<Registration> userDetails = new ArrayList<Registration>();
        processUploadedFile(uploadedFile,userDetails);

        model.addAttribute("userDetails", userDetails);

        return "registration";
    }

    private File uploadFile(Registration registration) {

        Date dt = new Date();
        SimpleDateFormat format = new SimpleDateFormat("MM_dd_yyyy_HH_mm_ss");
        File uploadedFile = new File(uploadFileLocation
                + registration.getFileData().getOriginalFilename() + "."
                + format.format(dt));

            registration.getFileData().transferTo(uploadedFile);

        return uploadedFile;
    }

    private void processUploadedFile(File uploadedFile, List<Registration> userDetails)
            throws NumberFormatException, Exception {

        registrationService.processFile(uploadedFile, userDetails);
    }

}

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

Edit Я записываю следующий тестовый класс, но как продолжить дальше

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(locations = { "/META-INF/spring/applicationContext.xml"})
public class BulkRegistrationControllerTest {

    @InjectMocks
    private RegistrationService registrationService= new RegistrationServiceImpl();
    @Mock
    private final ModelMap model=new ModelMap(); 

    @InjectMocks
    private ApplicationContext applicationContext;

    private static MockHttpServletRequest request;
    private static MockHttpServletResponse response;

    private static RegistrationController registrationController;

    @BeforeClass
    public static void init() {

           request = new MockHttpServletRequest();
           response = new MockHttpServletResponse();           
           registrationController = new RegistrationController();

    }
    public void testCreate()
    {
        final String target = "bulkRegistration";
        BulkRegistration bulkRegistration=new BulkRegistration();
        final BindingResult result=new BindingResult();     

        String nextPage=null;       
        nextPage = bulkRegistrationController.create(bulkRegistration, result, model);
        assertEquals("Controller is not requesting the correct form",nextPage,
                target);

    }

}

Ответ 1

Есть несколько вещей, которые вы, похоже, перешли в свой тест. Существуют интеграционные тесты и модульные тесты. Интеграционные тесты будут проверять все (или почти все) все подключенные - так что вы используете файлы конфигурации Spring, очень близкие к реальным, и реальные примеры объектов вводятся в тестируемый класс. В основном я использую @ContextConfiguration, но я использую это в сочетании с @RunWith (SpringJUnit4ClassRunner.class)

Если вы используете Mockito (или любую фальшивую фреймворк), это обычно потому, что вы хотите изолировать класс, который вы тестируете, от реальных реализаций других классов. Таким образом, вместо того, чтобы, например, придумать способ заставить вашу регистрационную службу генерировать исключение NumberFormatException для проверки этого пути кода, вы просто скажите, что это mock RegistrationService. Существует множество других примеров, где удобнее использовать mocks, чем использовать экземпляры реального класса.

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

@RunWith(MockitoJUnitRunner.class)
public class RegistrationControllerTest {

    // Create an instance of what you are going to test.
    // When using the @InjectMocks annotation, you must create the instance in
    // the constructor or in the field declaration.
    @InjectMocks
    private RegistrationController controllerUT = new RegistrationController();

    // The @Mock annotation creates the mock instance of the class and
    // automatically injects into the object annotated with @InjectMocks (if
    // possible).
    @Mock
    private RegistrationService registrationService;
    // This @Mock annotation simply creates a mock instance. There is nowhere to
    // inject it. Depending on the particular circumstance, it may be better or
    // clearer to instantiate the mock explicitly in the test itself, but we're
    // doing it here for illustration. Also, I don't know what your real class
    // is like, but it may be more appropriate to just instantiate a real one
    // than a mock one.
    @Mock
    private ModelMap model;
    // Same as above
    @Mock
    private BulkRegistration bulkRegistration;
    // Same as above
    @Mock
    private FileData fileData;

    @Before
    public void setUp() {
        // We want to make sure that when we call getFileData(), it returns
        // something non-null, so we return the mock of fileData.
        when(bulkRegistration.getFileData()).thenReturn(fileData);
    }

    /**
     * This test very narrowly tests the correct next page. That is why there is
     * so little expectation setting on the mocks. If you want to test other
     * things, such as behavior when you get an exception or having the expected
     * filename, you would write other tests.
     */
    @Test
    public void testCreate() throws Exception {
        final String target = "bulkRegistration";
        // Here we create a default instance of BindingResult. You don't need to
        // mock everything.
        BindingResult result = new BindingResult();

        String nextPage = null;
        // Perform the action
        nextPage = controllerUT.create(bulkRegistration, result, model);
        // Assert the result. This test fails, but it for the right reason -
        // you expect "bulkRegistration", but you get "registration".
        assertEquals("Controller is not requesting the correct form", nextPage,
                target);

    }

    /**
     * Here is a simple example to simulate an exception being thrown by one of
     * the collaborators.
     * 
     * @throws Exception
     */
    @Test(expected = NumberFormatException.class)
    public void testCreateWithNumberFormatException() throws Exception {
        doThrow(new NumberFormatException()).when(registrationService)
                .processFile(any(File.class), anyList());
        BindingResult result = new BindingResult();
        // Perform the action
        controllerUT.create(bulkRegistration, result, model);
    }
}

Ответ 2

Реальный вопрос: как настроить тестовую среду вашего приложения, которое использует Spring? Ответ на этот вопрос не прост, он действительно зависит от того, как работает ваше веб-приложение.

Сначала вы должны сосредоточиться на том, как сделать JUnit веб-приложение Java, а затем о том, как использовать Mockito.

Ответ 3

Определенно можно написать чистые модульные тесты для контроллеров Spring MVC, высмеивая их зависимости с Mockito (или JMock) как показанные выше. Остается проблема, заключающаяся в том, что с аннотированными контроллерами POJO существует много, что остается непроверенным - по существу все, что выражается в аннотации и выполняется каркасом при вызове контроллера.

Поддерживается тестирование Spring контроллеров MVC (см. spring -test-mvc project). Хотя проект все еще будет претерпевать изменения, он будет использоваться в его нынешнем виде. Если вы чувствительны к изменениям, вы не должны зависеть от этого. В любом случае я чувствовал, что стоит указать, хотите ли вы отследить его или принять участие в его разработке. Существует ночной снимок, и в этом месяце будет выпущена веха, если вы хотите заблокировать определенную версию.

Ответ 4

Mockito - это насмешливая структура, которая используется для издевательства объектов. Это обычно возможно, когда вы тестируете метод, который зависит и от какого-то другого результата метода объекта. Например, при тестировании вашего метода создания вы хотели бы издеваться над переменной uploadedFile, так как здесь вам неинтересно тестировать, работает ли uploadFile(Registration registration) (вы проверяете его в каком-то другом тесте), но вы, re, котор нужно испытать если метод обрабатывает uploaded архив и если он добавляет details в модели. Чтобы высмеять файл загрузки, вы можете пойти: when(RegistrationController.uploadFile(anyObject()).thenReturn(new File());

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

Вы должны помнить, что если ваш код трудно тестировать, это означает, что вы не сделали все возможное, чтобы оно было простым.

Ответ 5

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

  • Точка использования Mockito - это издеваться над зависимостями вашего класса. Это позволит вам использовать простой тестовый пример JUnit. Поэтому нет необходимости использовать @ContextConfiguration. Вы должны иметь возможность создавать экземпляр класса, тестируемого с использованием нового оператора, а затем предоставлять необходимые зависимости.

  • Вы используете Autowiring для предоставления своего регистрационного сервиса. Чтобы внедрить интуитивно понятный экземпляр этой службы, вам нужно будет использовать утилиты приватного доступа для доступа Spring.

  • Я не могу видеть из вашего кода, является ли RegistrationService интерфейсом. Если это не так, у вас будут проблемы с издевательством.

Ответ 6

Я не знаком с Mockito (потому что я использую JMock), но общий подход к написанию тестов с помощью mocks одинаковый.

Сначала вам нужен экземпляр тестируемого класса (CUT) (RegistrationController). Это НЕ должно быть издевательством - потому что вы хотите проверить его.

Для тестирования getUploadForm CUT-экземпляр не нуждается в каких-либо зависимостях, поэтому вы можете создать его через new RegistrationController.

Тогда у вас должна быть тестовая шляпа, немного похожая на это

RegistrationController controller = new RegistrationController();
Model model = new Model();
String result = controller(model);
assertEquals("is/Registration", result);
assertSomeContstrainsFormodel

Это было легко.

Следующий метод, который вы хотите протестировать, - это create Метод. Это намного сложнее.

  • Вам нужно иметь экземпляр объектов параметров (BindingResult) может быть немного сложнее
  • Вам нужно обрабатывать файлы в тесте (удалять их потом) - я не буду обсуждать эту проблему. Но можете ли вы подумать о том, как использовать временные файлы для теста.
  • Вы используете обе переменные registrationService и uploadFileLocation - это интересная часть.

uploadFileLocation - это просто поле, которое должно быть установлено в тесте. Самый простой способ - добавить (getter и) setter, чтобы установить, что было подано в тесте. Вы также можете использовать org.springframework.test.util.ReflectionTestUtils для установки этого поля. - в обоих направлениях есть плюсы и связи.

Более интересным является registrationService. Это должно быть Mock! Вам нужно создать Mock для этого класса, а затем "ввести", что макет в экземпляре CUT. Как и для uploadFileLocation, у вас есть как минимум два одинаковых варианта.

Затем вам нужно определить исключения, которые у вас есть для макета: этот registrationService.processFile(uploadedFile, userDetails) вызывается с правильными данными файла и пользователя. (насколько точно это исключение определено, является частью Mockito - и у меня недостаточно знаний).

Затем вам нужно вызвать метод, который вы хотите протестировать на CUT.

BTW: Если вам нужно "вводить" издевательства на Spring beans очень часто, тогда вы можете создать свой собственный ресурс. Получив экземпляр объекта, сканируйте этот объект для полей с аннотациями @Inject, создайте для этого Mocks и "введете", что издевается. (Тогда вам нужно только получить getter для доступа к mocks, чтобы определить там ожидания.) - У меня есть такой инструмент для JMock, и это очень помогло мне.

Ответ 7

Альтернативное предложение: не используйте Mockito. Spring имеет свои собственные классы тестирования, которые можно использовать для издевки, и вы можете использовать SpringJUnit4ClassRunner. Использование тестового бегунка Spring JUnit позволяет загружать полную конфигурацию Spring (через @ContextConfiguration), а также имитировать объекты. В вашем случае большая часть кода вашего экземпляра исчезает, потому что вы будете запускать Spring, а не имитировать его DI.

Ответ 8

Попробуйте это.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/META-INF/spring/applicationContext.xml"})
public class BulkRegistrationControllerTest {

    @Mock
    private RegistrationService registrationService;

    //Controller that is being tested.
    @Autowired
    @InjectMocks
    private RegistrationController registrationController;

    @Before
    public void setUp() {
       MockitoAnnotations.initMocks(this);
       ...
    }
    ...