├── .travis.yml
├── src
├── main
│ └── java
│ │ └── com
│ │ └── blog
│ │ └── samples
│ │ ├── model
│ │ ├── EnumAccountType.java
│ │ └── Account.java
│ │ ├── exception
│ │ ├── AccountNotFoundException.java
│ │ ├── InvalidAccountRequestException.java
│ │ └── components
│ │ │ └── ControllerExceptionHandler.java
│ │ ├── service
│ │ └── AccountService.java
│ │ ├── Application.java
│ │ └── controller
│ │ └── AccountController.java
├── .project
├── .classpath
└── test
│ └── java
│ └── com
│ └── blog
│ └── samples
│ └── controller
│ └── AccountControllerTest.java
├── README.md
└── pom.xml
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 | script: mvn clean install
5 |
--------------------------------------------------------------------------------
/src/main/java/com/blog/samples/model/EnumAccountType.java:
--------------------------------------------------------------------------------
1 | package com.blog.samples.model;
2 |
3 | public enum EnumAccountType {
4 |
5 | CURRENT, SAVINGS
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/blog/samples/exception/AccountNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.blog.samples.exception;
2 |
3 | public class AccountNotFoundException extends RuntimeException {
4 |
5 | private static final long serialVersionUID = 2468434988680850339L;
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/blog/samples/exception/InvalidAccountRequestException.java:
--------------------------------------------------------------------------------
1 | package com.blog.samples.exception;
2 |
3 | public class InvalidAccountRequestException extends RuntimeException {
4 |
5 | private static final long serialVersionUID = 2468434988680850339L;
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/blog/samples/service/AccountService.java:
--------------------------------------------------------------------------------
1 | package com.blog.samples.service;
2 |
3 | import com.blog.samples.model.Account;
4 |
5 | public interface AccountService {
6 |
7 | public Account loadAccount(Long accountId);
8 |
9 | public Long createAccount(Account account);
10 | }
--------------------------------------------------------------------------------
/src/main/java/com/blog/samples/Application.java:
--------------------------------------------------------------------------------
1 | package com.blog.samples;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class Application {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(Application.class, args);
11 | }
12 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/briansjavablog/rest-controller-testing-mock-mvc)
2 |
3 | # REST Controller Testing WIth MockMVC
4 | A simple REST controller and some unit tests using Springs MockMVC
5 |
6 | The blog post associated with this sample code can be found at https://www.briansdevblog.com/2017/05/rest-endpoint-testing-with-mockmvc
7 |
--------------------------------------------------------------------------------
/src/main/java/com/blog/samples/model/Account.java:
--------------------------------------------------------------------------------
1 | package com.blog.samples.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | public class Account {
7 |
8 | @Getter
9 | @Setter
10 | private Long accountId;
11 |
12 | @Getter
13 | @Setter
14 | private EnumAccountType accountType;
15 |
16 | @Getter
17 | @Setter
18 | private Double balance;
19 |
20 | public Account(){}
21 |
22 | public Account(Long accountId, EnumAccountType accountType, Double balance){
23 | this.accountId = accountId;
24 | this.accountType = accountType;
25 | this.balance = balance;
26 | }
27 |
28 | public Account(EnumAccountType accountType, Double balance){
29 | this.accountType = accountType;
30 | this.balance = balance;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | rest-endpoint-testing
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.wst.common.project.facet.core.builder
10 |
11 |
12 |
13 |
14 | org.eclipse.jdt.core.javabuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.m2e.core.maven2Builder
20 |
21 |
22 |
23 |
24 | org.springframework.ide.eclipse.core.springbuilder
25 |
26 |
27 |
28 |
29 |
30 | org.springframework.ide.eclipse.core.springnature
31 | org.eclipse.jdt.core.javanature
32 | org.eclipse.m2e.core.maven2Nature
33 | org.eclipse.wst.common.project.facet.core.nature
34 |
35 |
36 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | org.springframework
6 | rest-controller-testing-mock-mvc
7 | 0.1.1
8 |
9 | org.springframework.boot
10 | spring-boot-starter-parent
11 | 2.1.5.RELEASE
12 |
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-web
17 |
18 |
19 | org.projectlombok
20 | lombok
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter-test
25 | test
26 |
27 |
28 |
29 | 1.8
30 |
31 |
--------------------------------------------------------------------------------
/src/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/main/java/com/blog/samples/exception/components/ControllerExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.blog.samples.exception.components;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ControllerAdvice;
5 | import org.springframework.web.bind.annotation.ExceptionHandler;
6 | import org.springframework.web.bind.annotation.ResponseStatus;
7 |
8 | import com.blog.samples.exception.AccountNotFoundException;
9 | import com.blog.samples.exception.InvalidAccountRequestException;
10 |
11 | import lombok.extern.slf4j.Slf4j;
12 |
13 | @Slf4j
14 | @ControllerAdvice
15 | public class ControllerExceptionHandler {
16 |
17 |
18 | @ResponseStatus(HttpStatus.NOT_FOUND) // 404
19 | @ExceptionHandler(AccountNotFoundException.class)
20 | public void handleNotFound(AccountNotFoundException ex) {
21 | log.error("Requested account not found");
22 | }
23 |
24 | @ResponseStatus(HttpStatus.BAD_REQUEST) // 400
25 | @ExceptionHandler(InvalidAccountRequestException.class)
26 | public void handleBadRequest(InvalidAccountRequestException ex) {
27 | log.error("Invalid account supplied in request");
28 | }
29 |
30 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 500
31 | @ExceptionHandler(Exception.class)
32 | public void handleGeneralError(Exception ex) {
33 | log.error("An error occurred procesing request" + ex);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/blog/samples/controller/AccountController.java:
--------------------------------------------------------------------------------
1 | package com.blog.samples.controller;
2 |
3 | import javax.servlet.http.HttpServletResponse;
4 |
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.http.HttpStatus;
7 | import org.springframework.web.bind.annotation.PathVariable;
8 | import org.springframework.web.bind.annotation.RequestBody;
9 | import org.springframework.web.bind.annotation.RequestMapping;
10 | import org.springframework.web.bind.annotation.RequestMethod;
11 | import org.springframework.web.bind.annotation.RestController;
12 | import org.springframework.web.context.request.WebRequest;
13 |
14 | import com.blog.samples.exception.AccountNotFoundException;
15 | import com.blog.samples.exception.InvalidAccountRequestException;
16 | import com.blog.samples.model.Account;
17 | import com.blog.samples.service.AccountService;
18 |
19 | @RestController
20 | public class AccountController {
21 |
22 | private AccountService accountService;
23 |
24 | @Autowired
25 | public AccountController(AccountService accountService) {
26 | this.accountService = accountService;
27 | }
28 |
29 | @RequestMapping(value = { "/api/account" }, method = { RequestMethod.POST })
30 | public Account createAccount(@RequestBody Account account,
31 | HttpServletResponse httpResponse,
32 | WebRequest request) {
33 |
34 | Long accountId = accountService.createAccount(account);
35 | account.setAccountId(accountId);
36 |
37 | httpResponse.setStatus(HttpStatus.CREATED.value());
38 | httpResponse.setHeader("Location", String.format("%s/api/account/%s",
39 | request.getContextPath(), accountId));
40 | return account;
41 | }
42 |
43 | @RequestMapping(value = "/api/account/{accountId}", method = RequestMethod.GET)
44 | public Account getAccount(@PathVariable("accountId") Long accountId) {
45 |
46 | /* validate account Id parameter */
47 | if (accountId < 9999) {
48 | throw new InvalidAccountRequestException();
49 | }
50 |
51 | Account account = accountService.loadAccount(accountId);
52 | if(null==account){
53 | throw new AccountNotFoundException();
54 | }
55 |
56 | return account;
57 | }
58 |
59 | }
--------------------------------------------------------------------------------
/src/test/java/com/blog/samples/controller/AccountControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.blog.samples.controller;
2 | import static org.mockito.ArgumentMatchers.any;
3 | import static org.mockito.Mockito.when;
4 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
5 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
6 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
7 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
8 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
9 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
10 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
11 |
12 | import org.junit.Before;
13 | import org.junit.Test;
14 | import org.junit.runner.RunWith;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.boot.test.context.SpringBootTest;
17 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
18 | import org.springframework.boot.test.mock.mockito.MockBean;
19 | import org.springframework.http.MediaType;
20 | import org.springframework.test.context.junit4.SpringRunner;
21 | import org.springframework.test.web.servlet.MockMvc;
22 | import org.springframework.web.context.WebApplicationContext;
23 |
24 | import com.blog.samples.Application;
25 | import com.blog.samples.model.Account;
26 | import com.blog.samples.model.EnumAccountType;
27 | import com.blog.samples.service.AccountService;
28 |
29 | @RunWith(SpringRunner.class)
30 |
31 | @SpringBootTest(webEnvironment=WebEnvironment.MOCK, classes={ Application.class })
32 | public class AccountControllerTest {
33 |
34 | private MockMvc mockMvc;
35 |
36 | @Autowired
37 | private WebApplicationContext webApplicationContext;
38 |
39 | @MockBean
40 | private AccountService accountServiceMock;
41 |
42 | @Before
43 | public void setUp() {
44 | this.mockMvc = webAppContextSetup(webApplicationContext).build();
45 | }
46 |
47 | @Test
48 | public void should_CreateAccount_When_ValidRequest() throws Exception {
49 |
50 | when(accountServiceMock.createAccount(any(Account.class))).thenReturn(12345L);
51 |
52 | mockMvc.perform(post("/api/account")
53 | .contentType(MediaType.APPLICATION_JSON)
54 | .content("{ \"accountType\": \"SAVINGS\", \"balance\": 5000.0 }")
55 | .accept(MediaType.APPLICATION_JSON))
56 | .andExpect(status().isCreated())
57 | .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
58 | .andExpect(header().string("Location", "/api/account/12345"))
59 | .andExpect(jsonPath("$.accountId").value("12345"))
60 | .andExpect(jsonPath("$.accountType").value("SAVINGS"))
61 | .andExpect(jsonPath("$.balance").value(5000));
62 | }
63 |
64 | @Test
65 | public void should_GetAccount_When_ValidRequest() throws Exception {
66 |
67 | /* setup mock */
68 | Account account = new Account(12345L, EnumAccountType.SAVINGS, 5000.0);
69 | when(accountServiceMock.loadAccount(12345L)).thenReturn(account);
70 |
71 | mockMvc.perform(get("/api/account/12345")
72 | .accept(MediaType.APPLICATION_JSON))
73 | .andExpect(status().isOk())
74 | .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
75 | .andExpect(jsonPath("$.accountId").value(12345))
76 | .andExpect(jsonPath("$.accountType").value("SAVINGS"))
77 | .andExpect(jsonPath("$.balance").value(5000.0));
78 | }
79 |
80 | @Test
81 | public void should_Return404_When_AccountNotFound() throws Exception {
82 |
83 | /* setup mock */
84 | when(accountServiceMock.loadAccount(12345L)).thenReturn(null);
85 |
86 | mockMvc.perform(get("/api/account/12345")
87 | .accept(MediaType.APPLICATION_JSON))
88 | .andExpect(status().isNotFound());
89 | }
90 |
91 | }
--------------------------------------------------------------------------------