【问题标题】:What is the best way to test Controllers and Services with JUnit?使用 JUnit 测试控制器和服务的最佳方法是什么?
【发布时间】:2016-01-14 15:18:51
【问题描述】:

我有这个 Spring MVC 控制器:

@Controller
@RequestMapping(value = "/foo")
public class FooController {

    @Inject
    private FooService fooService;

    @RequestMapping(value = "foo/new")
    public final String add(final ModelMap model) {
        model.addAttribute(fooService.createFoo());
        return "foo/detail";
    }

    @RequestMapping(value = "foo/{id}")
    public final String detail(final ModelMap model, @PathVariable long id) {
        model.addAttribute(fooService.findById(id));
        return "foo/detail";
    }

    @RequestMapping(value="foo/update", method=RequestMethod.POST)
    public final String save(@Valid @ModelAttribute final Foo foo, final BindingResult result, final SessionStatus status,
            final RedirectAttributes ra, final HttpServletRequest request) {

        if (result.hasErrors()) {
            return "foo/detail";
        }

        fooService.save(foo);
        status.setComplete();
        Message.success(ra, "message.ok");

        return "redirect:foo/list";
    }


    @RequestMapping( value= "/foo/delete/{id}", method=RequestMethod.POST)
    public String delete(@PathVariable final Long id, final SessionStatus status, final RedirectAttributes ra, final HttpServletRequest request){

        if (fooService.findByIdWithOtherFoos(id).getOtherFoos().isEmpty()) {
            fooService.delete(id);
            status.setComplete();
            MessageHelper.success(ra, "message.sucess");
        } else {
            Message.error(ra, "message.error");
        }

        return "redirect:foo/list";
    }
}

还有这项服务:

@Service
@Transactional(readOnly = true)
public class FooServiceImpl implements FooService {

    @Inject
    private fooRepository fooRepo;

    @Override
    public final Foo createFoo() {
        return new Foo();
    }

    @Override
    @Transactional(readOnly = false)
    public final void save(final Foo foo) {

        if (foo.getId() == null) {
            foo.setDate(new Date());
        }

        fooRepo.save(foo);
    }

    @Override
    @Transactional(readOnly = false)
    public final void delete(final Long id) {
        fooRepo.delete(id);
    }

    @Override
    public final Foo findById(final Long id) {
        return fooRepo.findOne(id);
    }

    @Override
    public Foo findByIdWithOtherFoos(Long id) {
        Foo foo = fooRepo.findOne(id);
        Hibernate.initialize(foo.getOtherFoos());
        return foo;
    }

    @Override
    public final Page<Foo> findAll(final Pageable pageable) {
        return fooRepo.findAll(pageable);
    }

    @Override
    public final Page<Foo> find(final String filter, final Pageable pageable) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public final List<Foo> findAll(final Sort sort) {
        return fooRepo.findAll(sort);
    }

}

使用 JUnit 驱动程序和服务进行测试以涵盖所有逻辑条件的最佳方法是什么? 我总是以一堆测试行结束所有逻辑条件。

我们推荐使用 MockitoJUnitRunner?或者创建创建配置 bean 的类。并使用 ContextConfiguration 'ContextConfiguration (FooServiceImplTestConfiguration.class classes = {})' 向他们收费

如何实现 Given-When-Then 模式?

【问题讨论】:

  • “最佳”方式相当广泛。您可以使用此资源以多种不同方式测试很多东西,但没有一种方式是整体最佳方式。
  • "覆盖所有逻辑条件"是要求进行全面测试,这对于现实代码来说是不切实际的。
  • 你想测试什么?控制器中的 Java 代码?您的服务层中的 Java 代码? @RequestMapping?使用的消息转换器?如果你想测试所有这些,这个问题太宽泛了。如果你只想测试一个,这个问题就不清楚了。
  • 不要将您的解决方案添加到您的问题中;将其作为答案单独发布。

标签: java spring junit


【解决方案1】:

在测试控制器(尤其是集成测试)时,我建议使用Spring's MockMVCRest-Assured。下面是使用 Rest-Assured 的示例:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SomeApplication.class)
@WebIntegrationTest(randomPort = true)
@ActiveProfiles(profiles = "test")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class SomeControllerTest {

    @Test
    public void getAllSomeObjects() {
        expect().statusCode(HttpStatus.SC_OK)
                .body("", hasSize(2))
                .body("[0]", notNullValue())
                .body("[1]", notNullValue())
                .body("findAll { it.name.equals('TEST1') }", hasSize(1))
                .body("findAll { it.name.equals('TEST2') }", hasSize(1))
                .when()
                .get("/someAddress");
    }
}

对于测试服务,我建议使用Mockito。此外,Hamcrest Matchers 是一个用于测试断言的有用库。使用以下两者的示例:

public class SomeServiceTest {

    @InjectMocks
    private SomeService someService;

    @Mock
    private SomeInnerService someInnerService;

    @Before
    public void setUp() {
        initMocks(this);
        Mockito.when(someInnerService.useMethod("argument")).thenReturn(new SomeObject());
    }

    @Test
    public void testSomeMethod() {
        Set<SomeObject> someObjects= someService.someMethod();
        assertThat(someObjects, is(notNullValue()));
        assertThat(someObjects, is(hasSize(4)));
    }

}

【讨论】:

    【解决方案2】:

    您应该独立测试两者。

    首先为您的服务创建一个单元测试。您可以使用 Mockito 将您的服务依赖项模拟为 fooRepository。

    @Test
    public void testFindById() {
        when(fooServices.findById(123)).thenReturn(fooSample);
        assertThat(what you want);
    }
    

    然后,您应该为您的控制器创建另一个单元测试。最简单的方法是使用 spring-test 中提供的MockMvc。在这种情况下,您可以使用 Mockito 来模拟 fooService。

    【讨论】:

      【解决方案3】:

      最好的部分。使用 spring MVC 测试层。因为他们提供了自己的 API,可以帮助您模拟控制器并为您提供可以填充所需状态的会话对象。你可以在网上找到很多例子。 http://www.petrikainulainen.net/spring-mvc-test-tutorial/ 您实际上可以单独测试所有图层.. 一切顺利!

      【讨论】:

        【解决方案4】:

        看看Spring-Test-MVC。这是一个正是为此目的的框架,并附带了许多易于理解和重建的示例。

        我个人将 Mockito / PowerMock 添加到混合中以模拟内部依赖项。

        祝你好运。

        【讨论】:

          【解决方案5】:

          这取决于您要实施什么样的测试。

          当然Spring Test 对此有所帮助。该模块支持“单元”和集成测试。请注意,单元测试并不是真正的单元测试,因为在最少使用 Spring Test 时会涉及一些上下文加载。

          检查可用于向控制器发出请求的 MockMvc 类。

          【讨论】:

            【解决方案6】:

            我认为最好的方法是将ContextConfigurationDirtiesContextMockMvcBuilders 和Mockito 结合使用。这为您提供了通过应用程序上下文创建 Spring 控制器并注入其行为通过 Mockito 定义的 bean 的优势。在这种情况下,您可以达到较高的线路和条件覆盖率。这是您的代码示例:

            @ContextConfiguration
            @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
            @RunWith(SpringJUnit4ClassRunner.class)
            public class FooControllerTest {
            
                private MockMvc mockMvc;
            
                @Autowired
                private FooService service;
            
                @Autowired
                private FooController controller;
            
                @Before
                public void initController() {
                    mockMvc = MockMvcBuilders.standaloneSetup(frontEndController).build();
                }
            
                @Test
                public void shouldDoSomeThing_CornerCase() {
                    // Given: 
                    // define the behaviour of service with when(service...)
            
                    // Then:
                    // perform a request on contoller
                    mockMvc.perform(get("/foo/delete/{id}"))
            
                    // When:
                    // user Mockito verify 
                    // or
                    // MockMvcRequestBuilders
                }
            
                @Configuration
                public static class FooConfiguration {
            
                    @Bean
                    public FooController controller() {
                        return new FooController();
                    }
            
                    @Bean
                    public FooService service() {
                        return mock(FooService.class);
                    }
            
                }
            }
            

            DirtiesContext 很重要,这样您每次测试都能获得干净的模拟。

            【讨论】:

            • 我使用这种方式但是我有一个异常来初始化mockMvc。我用教程链接编辑问题
            【解决方案7】:

            最后我使用了这个解决方案。

            对于我的领域模型,我使用此链接http://www.javacodegeeks.com/2014/09/tips-for-unit-testing-javabeans.html

            /**
             * @param <T>
             */
            public abstract class AbstractJavaBeanTest<T> {
            
                protected String[] propertiesToBeIgnored;
            
            
                protected abstract T getBeanInstance();
            
                @Test
                public void beanIsSerializable() throws Exception {
                    final T myBean = getBeanInstance();
                    final byte[] serializedMyBean = SerializationUtils.serialize((Serializable) myBean);
                    @SuppressWarnings("unchecked")
                    final T deserializedMyBean = (T) SerializationUtils.deserialize(serializedMyBean);
                    assertEquals(myBean, deserializedMyBean);
                }
            
            
                @Test
                public void equalsAndHashCodeContract() {
                    EqualsVerifier.forClass(getBeanInstance().getClass()).suppress(Warning.STRICT_INHERITANCE, Warning.NONFINAL_FIELDS).verify();
                }
            
            
                @Test
                public void getterAndSetterCorrectness() throws Exception {
                    final BeanTester beanTester = new BeanTester();
                    beanTester.getFactoryCollection().addFactory(LocalDateTime.class, new LocalDateTimeFactory());
                    beanTester.testBean(getBeanInstance().getClass());
                }
            
                class LocalDateTimeFactory implements Factory {
                    @Override
                    public LocalDateTime create() {
                        return LocalDateTime.now();
                    }
                }
            }
            
            /**
             * Test Foo
             */
            public class FooTest extends AbstractJavaBeanTest<Foo> {
            
                @Override
                protected Foo getBeanInstance() {
                    return new Foo();
                }
            
            }
            

            我将此依赖项添加到 pom.xml:

            <dependency>
                <groupId>nl.jqno.equalsverifier</groupId>
                <artifactId>equalsverifier</artifactId>
                <version>1.7.6</version>
                <scope>test</scope>
            </dependency>
            
            <dependency>
                <groupId>org.meanbean</groupId>
                <artifactId>meanbean</artifactId>
                <version>2.0.3</version>
            </dependency>
            

            对于我的 MVC 控制器,我使用此链接 http://www.luckyryan.com/2013/08/24/unit-test-controllers-spring-mvc-test/

            /**
             * Test FooController
             */
            public class FooControllerTest {
            
                @Mock
                private FooService fooService;
            
                @InjectMocks
                private FooController fooController;
            
                private MockMvc mockMvc;
            
                @Before
                public void setup() {
                    // Process mock annotations
                    MockitoAnnotations.initMocks(this);
            
                    // Setup Spring test in standalone mode
                    this.mockMvc = MockMvcBuilders.standaloneSetup(fooController).build();
                }
            
                @Test
                public void testAdd() throws Exception {
            
                    Foo foo = new Foo();
            
                    // given
                    given(FooService.createFoo()).willReturn(foo);
            
                    // when
                    // then
                    this.mockMvc.perform(get("/foo/new"))
                        .andExpect(forwardedUrl("foo/detail"))
                        .andExpect(model().attributeExists("foo"))
                        .andExpect(model().attribute("foo", is(foo)));
                }
            
                @Test
                public void testDetail() throws Exception {
            
                    Foo foo = new Foo();
                    Long fooId = 1L;
            
                    // given
                    given(fooService.findById(fooId)).willReturn(foo);
            
                    // when
                    // then
                    this.mockMvc.perform(get("/foo/" + fooId))
                        .andExpect(forwardedUrl("foo/detail"))
                        .andExpect(view().name("foo/detail"))
                        .andExpect(model().attributeExists("foo"))
                        .andExpect(model().attribute("foo", is(foo)));
                }
            
                @Test
                public void testSave() throws Exception {
            
                    Foo foo = new Foo();
            
                    // given
                    // when
                    // then
            
                    //With form errors
                    this.mockMvc.perform(post("/foo/update")
                            .param("name", "")
                            .sessionAttr("foo", foo))
                    .andExpect(forwardedUrl("foo/detail"))
                    .andExpect(model().hasErrors())
                    .andExpect(model().attributeHasFieldErrors("foo", "name"));
            
                    //Without form errores
                    this.mockMvc.perform(post("/foo/update")
                            .param("name", "nameValue")
                            .param("code", "codeValue")
                            .param("date", "20/10/2015")
                            .requestAttr("referer", "/foo/list")
                            .sessionAttr("foo", foo))
                    .andExpect(view().name("redirect:" + "/foo/list"))
                    .andExpect(model().hasNoErrors())
                    .andExpect(flash().attributeExists("message"))
                    .andExpect(flash().attribute("message", hasProperty("message", is("message.ok"))))
                    .andExpect(flash().attribute("message", hasProperty("type", is(Message.Type.SUCCESS))))
                    .andExpect(status().isFound());
                }
            
                @Test
                public void testDelete() throws Exception {
                    Foo foo = new Foo();
                    foo.setOtherFoos(new ArrayList<OtherFoo>());
                    Long fooId = 1L;
            
                    // given
                    given(fooService.findByIdWithOtherFoos(fooId)).willReturn(foo);
            
                    // when
                    // then
                    //Without errors: without other foos
                    this.mockMvc.perform(post("/foo/delete/" + fooId)
                            .sessionAttr("foo", foo)
                            .requestAttr("referer", "/foo/list"))
                    .andExpect(view().name("redirect:" + "/foo/list"))
                    .andExpect(flash().attributeExists("message"))
                    .andExpect(flash().attribute("message", hasProperty("message", is("message.ok"))))
                    .andExpect(flash().attribute("message", hasProperty("type", is(Message.Type.SUCCESS))));
            
            
                    // given
                    foo.getOtherFoos().add(new OtherFoo());
                    given(fooService.findByIdWithOtherFoos(fooId)).willReturn(foo);
            
                    // when
                    // then
                    //With errors: with other foos
                    this.mockMvc.perform(post("/foo/delete/" + fooId)
                            .sessionAttr("foo", foo)
                            .requestAttr("referer", "/foo/list"))
                    .andExpect(view().name("redirect:" + "/foo/list"))
                    .andExpect(flash().attributeExists("message"))
                    .andExpect(flash().attribute("message", hasProperty("message", is("message.error"))))
                    .andExpect(flash().attribute("message", hasProperty("type", is(Message.Type.DANGER))));
                }
            
            }
            

            对于我的 JUnit 服务测试,我为 Configuration 实现了一个类,并将其加载到服务测试中

            @Configuration
            public class FooServiceImplTestConfiguration {
            
                @Bean
                public FooService fooService() {
                    return new FooServiceImpl();
                }
            
                @Bean
                public FooRepository fooRepository() {
                    return Mockito.mock(FooRepository.class);
                }
            }
            
            @RunWith(SpringJUnit4ClassRunner.class)
            @ContextConfiguration(classes = {FooServiceImplTestConfiguration.class})
            public class FooServiceImplTest {
            
                @Inject
                private FooRepository fooRepository;;
            
                @Inject
                private FooService fooService;
            
                @BeforeClass
                public static void oneTimeSetUp() {
                    // one-time initialization code
                    System.out.println("@BeforeClass - oneTimeSetUp");
                }
            
                @AfterClass
                public static void oneTimeTearDown() {
                    // one-time cleanup code
                    System.out.println("@AfterClass - oneTimeTearDown");
                }
            
                @Before
                public void setUp() {
                }
            
                @After
                public void tearDown() {
                }
            
                @Test
                public void createFoo() {
                    assertNotNull(fooService.createFoo());
                }
            
                @Test
                public void save() {
            
                    //New foo
                    Foo saveFoo = new Foo();
                    // given
            
                    // when
                    fooService.save(saveFoo);
            
                    // then
                    assertNotNull(saveFoo.getDate());
            
                    saveFoo.setId(1L);
                    Date date = new Date();
                    saveFoo.setDate(date);
            
                    // given
            
                    //when
                    fooService.save(saveFoo);
            
                    //then
                    assertThat(date, is(saveFoo.getDate()));
                }
            
                @Test
                public void delete() {
            
                    //given
            
                    //when
                    fooService.deleteFoo(Matchers.anyLong());
            
                    //then
                }
            
                @Test
                public void findById() {
                    Long id = 1L;
                    Foo fooResult = new Foo();
            
                    //given
                    given(fooRepository.findOne(id)).willReturn(fooResult);
            
                    //when
                    Foo foo = fooService.findById(id);
            
                    //then
                    assertThat(foo, is(fooResult));
                }
            
                @Test
                public void findByIdWithOtherFoos() {
                    Long id = 1L;
                    Foo fooResult = new Foo();
            
                    //given
                    given(fooRepository.findOne(id)).willReturn(fooResult);
            
                    //when
                    Foo foo = fooService.findByIdWithOtherFoos(id);
            
                    //then
                    assertThat(foo, is(fooResult));
                }
            
                @Test
                public void findAll() {
                    Page<Foo> fooResult = new PageImpl<>(new ArrayList<Foo>());
            
                    given(fooRepository.findAll(Matchers.<Pageable>anyObject())).willReturn(fooResult);
            
                    //when
                    Page<Foo> foos = fooService.findAll(Matchers.<Pageable>anyObject());
            
                    //then
                    assertThat(foos, is(fooResult));
                }
            
                @Test
                public void findAllList() {
                    List<Foo> fooResult = new ArrayList<Foo>();
            
                    given(fooRepository.findAll(Matchers.<Sort>anyObject())).willReturn(fooResult);
            
                    //when
                    List<Foo> foos = fooService.findAll(Matchers.<Sort>anyObject());
            
                    //then
                    assertThat(foos, is(fooResult));
                }
            }
            

            在我的 pom 中我需要添加这个依赖项:

            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>3.2.3.RELEASE</version>
            </dependency>
            
            <!-- This is for mocking the service -->
            
            <dependency>
                <groupId>org.mockito</groupId>
                <artifactId>mockito-all</artifactId>
                <version>1.9.5</version>
                <scope>test</scope>
            </dependency>
            
            <!-- Optional -->
            <dependency>
                <groupId>org.hamcrest</groupId>
                <artifactId>hamcrest-core</artifactId>
                <version>1.3</version>
                <scope>test</scope>
            </dependency>
            
            <dependency>
                <groupId>org.hamcrest</groupId>
                <artifactId>hamcrest-library</artifactId>
                <version>1.3</version>
                <scope>test</scope>
            </dependency>
            

            我需要为此更改我的休眠验证器版本:

            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>5.1.3.Final</version>
            </dependency>
            

            我也需要添加这个依赖项,因为我得到了这个异常:

            原因:java.lang.AbstractMethodError:org.hibernate.validator.internal.engine.ConfigurationImpl.getDefaultParameterNameProvider()Ljavax/validation/ParameterNameProvider;

            详细信息:org.hibernate.validator.internal.engine.ConfigurationImpl.getDefaultParameterNameProvider()Ljavax/validation/ParameterNameProvider;

            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
            </dependency>
            
            <dependency>
                <groupId>org.glassfish.web</groupId>
                <artifactId>el-impl</artifactId>
                <version>2.2</version>
            </dependency>
            

            我正在使用 spring 数据,我也需要对我的自定义 CrudRepositories 进行测试。

            【讨论】:

              猜你喜欢
              • 2018-02-05
              • 2010-10-11
              • 1970-01-01
              • 1970-01-01
              • 2020-01-15
              • 2015-06-06
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多