使用JUnit测试控制器和服务的最佳方法是什么?

osc*_*car 11 java junit spring

我有这个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";
    }
}
Run Code Online (Sandbox Code Playgroud)

而这项服务:

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

}
Run Code Online (Sandbox Code Playgroud)

使用JUnit驱动程序和服务进行测试以覆盖所有逻辑条件的最佳方法是什么?我总是最终得到一堆测试线来涵盖所有逻辑条件.

我们建议使用MockitoJUnitRunner?或者创建创建配置bean的类.并使用ContextConfiguration为它们充电'ContextConfiguration (FooServiceImplTestConfiguration.class classes = {})'

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

Roz*_*art 5

在测试控制器(尤其是集成测试)时,我建议使用Spring 的 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

对于测试服务,我建议使用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)));
    }

}
Run Code Online (Sandbox Code Playgroud)


osc*_*car 0

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

对于我的域模型,我使用此链接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();
    }

}
Run Code Online (Sandbox Code Playgroud)

我将此依赖项添加到 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>
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

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

@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));
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的 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>
Run Code Online (Sandbox Code Playgroud)

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

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.1.3.Final</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

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

原因: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>
Run Code Online (Sandbox Code Playgroud)

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