Как написать тестовый блок для класса контроллера с помощью mockito - PullRequest
14 голосов
/ 17 января 2012

Я очень новичок в Mockito и jUnit, и я пытаюсь научиться правильно делать TDD. Мне нужны примерные пары, чтобы я мог написать модульный тест, используя 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?

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

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

    }

}

Ответы [ 8 ]

14 голосов
/ 17 января 2012

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

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

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

@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's 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 голосов
/ 20 января 2012

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

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

1 голос
/ 17 января 2012

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

Сначала вам нужен экземпляр тестируемого класса (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 и) для установки того, что подано в тесте. Вы также можете использовать org.springframework.test.util.ReflectionTestUtils для установки этого поля. - у обоих способов есть плюсы и минусы.

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

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

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

Кстати: если вам нужно очень часто "вставлять" макеты в бины Spring, вы можете создать свою собственную утилиту. Это получает экземпляр объекта, сканирует этот объект на наличие полей с аннотациями @Inject, создает Mocks для этого и «внедряет» это mocks. (Тогда вам нужен только getter для доступа к макетам, чтобы определить их ожидания.) - Я создал такой инструмент для JMock, и он мне очень помог.

1 голос
/ 17 января 2012

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

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

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

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

1 голос
/ 17 января 2012

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

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

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

1 голос
/ 17 января 2012

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

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

0 голосов
/ 30 мая 2012

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

@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);
       ...
    }
    ...
0 голосов
/ 17 января 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...