java 使用 JUnit 测试控制器和服务的最佳方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/34793104/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the best way to test Controllers and Services with JUnit?
提问by oscar
I have this Spring MVC controller:
我有这个 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";
}
}
And this Service:
而这项服务:
@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);
}
}
What is the best way of testing with JUnit drivers and services to cover all logical conditions? I always end up with a bunch of test lines to cover all logical conditions.
使用 JUnit 驱动程序和服务进行测试以覆盖所有逻辑条件的最佳方法是什么?我总是以一堆测试行结束来覆盖所有逻辑条件。
We recommend using MockitoJUnitRunner? Or create classes which create configuration beans. And charge them with ContextConfiguration 'ContextConfiguration (FooServiceImplTestConfiguration.class classes = {})'
我们推荐使用 MockitoJUnitRunner?或者创建创建配置 bean 的类。并通过 ContextConfiguration 向他们收费'ContextConfiguration (FooServiceImplTestConfiguration.class classes = {})'
How to implement the Given-When-Then pattern?
如何实现 Given-When-Then 模式?
采纳答案by oscar
Finally I use this solution.
最后我使用了这个解决方案。
For my Domain Model I use this link http://www.javacodegeeks.com/2014/09/tips-for-unit-testing-javabeans.html
对于我的域模型,我使用此链接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();
}
}
I add this dependencies to pom.xml:
我将此依赖项添加到 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>
For my MVC controllers I use this link http://www.luckyryan.com/2013/08/24/unit-test-controllers-spring-mvc-test/
对于我的 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))));
}
}
For my JUnit service test I implemented a class for Configuration and I load it in the service test
对于我的 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));
}
}
In my pom I need add this dependencies:
在我的 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>
I need change the my hibernate validator version for this:
我需要为此更改我的休眠验证器版本:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
I need add this dependencies too because I got this excecption:
我也需要添加这个依赖项,因为我得到了这个异常:
Cause: java.lang.AbstractMethodError:org.hibernate.validator.internal.engine.ConfigurationImpl.getDefaultParameterNameProvider()Ljavax/validation/ParameterNameProvider;
原因:java.lang.AbstractMethodError:org.hibernate.validator.internal.engine.ConfigurationImpl.getDefaultParameterNameProvider()Ljavax/validation/ParameterNameProvider;
Detail message: 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>
I'm using spring data, I need to do the test for my custom CrudRepositories too.
我正在使用 spring 数据,我也需要对我的自定义 CrudRepositories 进行测试。
回答by Rozart
When it comes to testing Controllers (especially integration testing) i suggest using Spring's MockMVCor Rest-Assured. And example of using Rest-Assured in action can be seen below:
在测试控制器(尤其是集成测试)时,我建议使用Spring 的 MockMVC或Rest-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");
}
}
For testing Services i suggest using Mockito. Additionally Hamcrest Matchersis a useful library for assertions in tests. Example of using both below:
对于测试服务,我建议使用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)));
}
}
回答by Quentin
You should test both independently.
您应该独立测试两者。
First create a unit test for your service. You can use Mockito to mock your service dependency as fooRepository.
首先为您的服务创建一个单元测试。您可以使用 Mockito 将您的服务依赖项模拟为 fooRepository。
@Test
public void testFindById() {
when(fooServices.findById(123)).thenReturn(fooSample);
assertThat(what you want);
}
Then, you should create an other unit test for your controller. The easiest way to do that is to use MockMvcprovided in spring-test. And in this case, you can use Mockito to mock fooService.
然后,您应该为您的控制器创建另一个单元测试。最简单的方法是使用spring-test 中提供的MockMvc。在这种情况下,您可以使用 Mockito 来模拟 fooService。
回答by Ashay Jain
Best Part. Use spring MVC test layer. As they are providing their own API's which helps you to mock controllers and provide you session objects which you can fill with required state. you can find lots of examples online. http://www.petrikainulainen.net/spring-mvc-test-tutorial/You can actually test all your layers seperately .. All the best !!
最好的部分。使用 spring MVC 测试层。因为他们提供了自己的 API,它可以帮助您模拟控制器并为您提供可以填充所需状态的会话对象。你可以在网上找到很多例子。 http://www.petrikainulainen.net/spring-mvc-test-tutorial/您实际上可以单独测试所有图层.. 一切顺利!
回答by Jan
Have a look at Spring-Test-MVC. That's a framework for exactly that purpose and comes with a lot of easy to understand and rebuild examples.
看看Spring-Test-MVC。这是一个完全用于此目的的框架,并带有许多易于理解和重建的示例。
Personally I add Mockito / PowerMock to the mix for mocking internal dependencies away.
我个人将 Mockito / PowerMock 添加到组合中以模拟内部依赖项。
Good luck.
祝你好运。
回答by Marco Ferrari
It depends on what kind of test you want to implement.
这取决于您要实施什么样的测试。
Certainly Spring Testhelps in this. This module supports "unit" and integration testing. Note that unit tests are not really true unit tests because there is a little bit of context loading involved while using Spring Test at it's minimum.
当然,Spring Test对此有帮助。该模块支持“单元”和集成测试。请注意,单元测试并不是真正的单元测试,因为在最低限度地使用 Spring Test 时会涉及到一点上下文加载。
Check the MockMvcclass that you can use to make requests to controllers.
检查可用于向控制器发出请求的MockMvc类。
回答by schrieveslaach
I think the best way is to use ContextConfiguration
in combination with DirtiesContext
, MockMvcBuilders
and Mockito. This gives you the advantage of creating a Spring controller through an application context and injecting beans whose behaviour is defined through Mockito. In this case you can reach high line and condition coverage. Here is an example for your code:
我认为最好的方法是ContextConfiguration
与DirtiesContext
,MockMvcBuilders
和 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
is important so that you get clean mocks a every test.
DirtiesContext
很重要,这样您才能在每次测试中获得干净的模拟。