【问题标题】:how to write unit test case for controller class using mockito如何使用 mockito 为控制器类编写单元测试用例
【发布时间】:2012-01-17 12:31:35
【问题描述】:

我对 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);

    }

}

【问题讨论】:

  • 我在这里问过一个类似的问题 > stackoverflow.com/questions/9138555/… 还有 2 个其他问题与帖子相关联。我正在使用 spring-test-mvc 框架来测试 REST 控制器。因此,希望我的问题中讨论的答案/代码对您有所帮助。祝你好运!

标签: java spring unit-testing spring-mvc mockito


【解决方案1】:

您似乎在测试中交叉了几件事。有集成测试和单元测试。集成测试将测试所有连接的所有内容(或几乎所有内容) - 因此您使用的 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);
    }
}

【讨论】:

  • 为了使这个工作记住添加规范的实现到 POM,例如:glassfish-embedded-all,否则你将有一个缺失代码错误。
【解决方案2】:

正如上面的 jherricks 所示,通过使用 Mockito(或 JMock)模拟它们的依赖关系,绝对可以为 Spring MVC 控制器编写纯单元测试。仍然存在的挑战是,对于带注释的 POJO 控制器,还有很多未测试的东西——本质上是在调用控制器时在注释中表达并由框架完成的所有内容。

对测试 Spring MVC 控制器的支持正在进行中(请参阅spring-test-mvc project)。虽然该项目仍将发生变化,但它可以以目前的形式使用。但是,如果您对更改很敏感,则不应依赖它。无论哪种方式,如果您想跟踪它或参与它的开发,我觉得值得指出。有一个夜间快照,如果你想锁定特定版本,本月会有一个里程碑版本。

【讨论】:

    【解决方案3】:

    真正的问题是:
    如何设置使用 Spring 的应用程序的集成测试环境
    这个问题的答案并不简单,它真的取决于你的网络应用程序是如何工作的

    您应该首先关注如何JUnit Java Web 应用程序,然后关注如何使用Mockito

    【讨论】:

      【解决方案4】:

      Mockito 是一个用于模拟对象的模拟框架。当您测试一个依赖于其他对象的方法结果的方法时,这通常是可行的。例如,在测试您的 create 方法时,您可能想要模拟 uploadedFile 变量,因为在这里您不想测试 uploadFile(Registration registration) 是否正常工作(您在其他一些测试中对其进行测试),但是您有兴趣测试该方法是否正在处理上传的文件以及是否在模型中添加details。要模拟上传文件,你可以去:when(RegistrationController.uploadFile(anyObject()).thenReturn(new File());

      但是您会发现这表明存在设计问题。您的方法uploadFile() 不应驻留在控制器中,而应驻留在其他一些实用程序类中。然后你可以@Mock 该实用程序类而不是控制器。

      您必须记住,如果您的代码难以测试,则表明您没有尽力保持简单。

      【讨论】:

        【解决方案5】:

        查看上面的代码示例,我发现了一些问题:

        1. 使用 Mockito 的目的是模拟类的依赖关系。这将使您能够使用简单的 JUnit 测试用例。因此不需要使用@ContextConfiguration。您应该能够使用 new 运算符实例化正在测试的类,然后提供所需的依赖项。

        2. 您正在使用自动装配来提供您的注册服务。为了注入此服务的模拟实例,您需要使用 Spring 测试私有字段访问实用程序。

        3. 我无法从您的代码中看出 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)可能有点复杂
          • 您需要处理测试中的文件(之后删除它们)- 我不会讨论这个问题。但是您是否应该想一种方法来使用临时文件进行测试。
          • 您同时使用了registrationServiceuploadFileLocation 这两个变量——这就是有趣的部分。

          uploadFileLocation 只是一个必须在测试中设置的字段。最简单的方法是添加一个(getter 和)setter 来设置测试中的文件。您也可以使用org.springframework.test.util.ReflectionTestUtils 来设置此字段。 -- 两种方式各有利弊。

          更有趣的是registrationService。这应该是一个Mock!您需要为该类创建一个 Mock,然后在 CUT 实例中“注入”该模拟。就像uploadFileLocation 一样,您至少有两个相同的选择。

          然后您需要定义模拟的例外情况:使用正确的文件和用户详细信息调用 registrationService.processFile(uploadedFile, userDetails)。 (这个异常的定义有多精确是 Mockito 的一部分 - 我没有足够的知识)。

          然后你需要调用你想在 CUT 上测试的方法。

          顺便说一句:如果您需要经常在 Spring bean 上“注入”模拟,那么您可以构建自己的实用程序。获取一个对象的实例,扫描该对象以查找带有 @Inject 注释的字段,为此创建 Mocks 并“注入”该模拟。 (然后你只需要 getter 来访问 mock 来定义期望。) -- 我已经为 JMock 构建了这样一个工具,它对我帮助很大。

          【讨论】:

            【解决方案7】:

            替代建议:不要使用 Mockito。 Spring 自带了自己的测试类,你可以用它来模拟,你可以使用SpringJUnit4ClassRunner。使用 Spring JUnit 测试运行器允许您加载完整的 Spring 配置(通过@ContextConfiguration)以及模拟对象。在您的情况下,您的大部分实例化代码都消失了,因为您将运行 Spring,而不是模仿它的 DI。

            【讨论】:

              【解决方案8】:

              试试这个。

              @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/META-INF/spring/applicationContext.xml"}) 公共类 BulkRegistrationControllerTest { @嘲笑 私人注册服务注册服务; //正在测试的控制器。 @自动连线 @InjectMocks 私人注册控制器注册控制器; @前 公共无效设置(){ MockitoAnnotations.initMocks(this); ... } ...

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2019-11-05
                • 2010-09-12
                • 1970-01-01
                • 1970-01-01
                • 2020-10-28
                • 1970-01-01
                • 1970-01-01
                • 2017-09-06
                相关资源
                最近更新 更多