Merge pull request #75 from strongant/master

完善章节3单元测试
This commit is contained in:
泥瓦匠BYSocket
2019-04-13 09:18:50 +08:00
committed by GitHub
6 changed files with 299 additions and 12 deletions

View File

@@ -60,4 +60,23 @@ public class Book implements Serializable {
public void setIntroduction(String introduction) {
this.introduction = introduction;
}
public Book(Long id, String name, String writer, String introduction) {
this.id = id;
this.name = name;
this.writer = writer;
this.introduction = introduction;
}
public Book(Long id, String name) {
this.id = id;
this.name = name;
}
public Book(String name) {
this.name = name;
}
public Book() {
}
}

View File

@@ -42,4 +42,18 @@ public interface BookService {
* @param id 编号
*/
Book findById(Long id);
/**
* 查找书是否存在
* @param book
* @return
*/
boolean exists(Book book);
/**
* 根据书名获取书籍
* @param name
* @return
*/
Book findByName(String name);
}

View File

@@ -2,24 +2,40 @@ package demo.springboot.service.impl;
import demo.springboot.domain.Book;
import demo.springboot.service.BookService;
import demo.springboot.web.BookController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* Book 业务层实现
*
* <p>
* Created by bysocket on 27/09/2017.
*/
@Service
public class BookServiceImpl implements BookService {
private static final AtomicLong counter = new AtomicLong();
/**
* 使用集合模拟数据库
*/
private static List<Book> books = new ArrayList<>(
Arrays.asList(
new Book(counter.incrementAndGet(), "book")));
// 模拟数据库,存储 Book 信息
// 第五章《数据存储》会替换成 MySQL 存储
private static Map<Long, Book> BOOK_DB = new HashMap<>();
private static Map<String, Book> BOOK_DB = new HashMap<>();
@Override
public List<Book> findAll() {
@@ -29,23 +45,40 @@ public class BookServiceImpl implements BookService {
@Override
public Book insertByBook(Book book) {
book.setId(BOOK_DB.size() + 1L);
BOOK_DB.put(book.getId(), book);
BOOK_DB.put(book.getId().toString(), book);
return book;
}
@Override
public Book update(Book book) {
BOOK_DB.put(book.getId(), book);
BOOK_DB.put(book.getId().toString(), book);
return book;
}
@Override
public Book delete(Long id) {
return BOOK_DB.remove(id);
return BOOK_DB.remove(id.toString());
}
@Override
public Book findById(Long id) {
return BOOK_DB.get(id);
return BOOK_DB.get(id.toString());
}
@Override
public boolean exists(Book book) {
return findByName(book.getName()) != null;
}
@Override
public Book findByName(String name) {
for (Book book : books) {
if (book.getName().equals(name)) {
return book;
}
}
return null;
}
}

View File

@@ -2,8 +2,14 @@ package demo.springboot.web;
import demo.springboot.domain.Book;
import demo.springboot.service.BookService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.List;
@@ -16,6 +22,10 @@ import java.util.List;
@RequestMapping(value = "/book")
public class BookController {
private final Logger LOG = LoggerFactory.getLogger(BookController.class);
@Autowired
BookService bookService;
@@ -43,8 +53,20 @@ public class BookController {
* 通过 @RequestBody 绑定实体参数,也通过 @RequestParam 传递参数
*/
@RequestMapping(value = "/create", method = RequestMethod.POST)
public Book postBook(@RequestBody Book book) {
return bookService.insertByBook(book);
public ResponseEntity<Void> postBook(@RequestBody Book book, UriComponentsBuilder ucBuilder) {
LOG.info("creating new book: {}", book);
if (book.getName().equals("conflict")){
LOG.info("a book with name " + book.getName() + " already exists");
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
bookService.insertByBook(book);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/book/{id}").buildAndExpand(book.getId()).toUri());
return new ResponseEntity<>(headers, HttpStatus.CREATED);
}
/**

View File

@@ -0,0 +1,198 @@
package demo.springboot.web;
import com.fasterxml.jackson.databind.ObjectMapper;
import demo.springboot.WebApplication;
import demo.springboot.domain.Book;
import demo.springboot.service.BookService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = WebApplication.class)
@AutoConfigureMockMvc
@TestPropertySource(locations = "classpath:application.properties")
public class BookControllerTest {
private MockMvc mockMvc;
@Mock
private BookService bookService;
@InjectMocks
private BookController bookController;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders
.standaloneSetup(bookController)
//.addFilters(new CORSFilter())
.build();
}
@Test
public void getBookList() throws Exception {
mockMvc.perform(get("/book")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$", hasSize(0)));
}
@Test
public void test_create_book_success() throws Exception {
Book book = createOneBook();
when(bookService.insertByBook(book)).thenReturn(book);
mockMvc.perform(
post("/book/create")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(book)))
.andExpect(status().isCreated())
.andExpect(header().string("location", containsString("/book/1")));
}
@Test
public void test_create_book_fail_404_not_found() throws Exception {
Book book = new Book(99L, "conflict");
when(bookService.exists(book)).thenReturn(true);
mockMvc.perform(
post("/book/create")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(book)))
.andExpect(status().isConflict());
}
@Test
public void test_get_book_success() throws Exception {
Book book = new Book(1L, "测试获取一本书", "strongant作者", "社区 www.spring4all.com 出版社出版");
when(bookService.findById(1L)).thenReturn(book);
mockMvc.perform(get("/book/{id}", 1L))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("$.id", is(1)))
.andExpect(jsonPath("$.name", is("测试获取一本书")));
verify(bookService, times(1)).findById(1L);
verifyNoMoreInteractions(bookService);
}
@Test
public void test_get_by_id_fail_null_not_found() throws Exception {
when(bookService.findById(1L)).thenReturn(null);
//TODO: 查找不到应该抛出 404 状态码, Demo 待优化
mockMvc.perform(get("/book/{id}", 1L))
.andExpect(status().isOk())
.andExpect(content().string(""));
verify(bookService, times(1)).findById(1L);
verifyNoMoreInteractions(bookService);
}
@Test
public void test_update_book_success() throws Exception {
Book book = createOneBook();
when(bookService.findById(book.getId())).thenReturn(book);
doReturn(book).when(bookService).update(book);
mockMvc.perform(
put("/book/update", book)
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(book)))
.andExpect(status().isOk());
}
@Test
public void test_update_book_fail_not_found() throws Exception {
Book book = new Book(999L, "测试书名1");
when(bookService.findById(book.getId())).thenReturn(null);
mockMvc.perform(
put("/book/update", book)
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(book)))
.andExpect(status().isOk())
.andExpect(content().string(""));
}
// =========================================== Delete Book ============================================
@Test
public void test_delete_book_success() throws Exception {
Book book = new Book(1L, "这本书会被删除啦");
when(bookService.findById(book.getId())).thenReturn(book);
doReturn(book).when(bookService).delete(book.getId());
mockMvc.perform(
delete("/book/delete/{id}", book.getId())
).andExpect(status().isOk());
}
@Test
public void test_delete_book_fail_404_not_found() throws Exception {
Book book = new Book(1L, "这本书会被删除啦");
when(bookService.findById(book.getId())).thenReturn(null);
mockMvc.perform(
delete("/book/delete/{id}", book.getId()))
.andExpect(status().isOk());
}
public static String asJsonString(final Object obj) {
try {
final ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private Book createOneBook() {
Book book = new Book();
book.setId(1L);
book.setName("测试书名1");
book.setIntroduction("这是一本 www.spring4all.com 社区出版的很不错的一本书籍");
book.setWriter("strongant");
return book;
}
}

View File

@@ -0,0 +1 @@
server.port=9090