├── .gitignore
├── README.md
├── pom.xml
├── src
└── main
│ ├── java
│ └── com
│ │ └── demo
│ │ └── mockservice
│ │ ├── App.java
│ │ ├── config
│ │ ├── FilterConfig.java
│ │ ├── HttpTraceActuatorConfiguration.java
│ │ └── TraceRequestFilter.java
│ │ ├── controller
│ │ └── MockServerDemoController.java
│ │ ├── dto
│ │ ├── Book.java
│ │ ├── BookSearchRequest.java
│ │ ├── BookSearchResponse.java
│ │ └── DynamicResponseBookSearchRequest.java
│ │ ├── filter
│ │ ├── DynamicResponseFilter.java
│ │ └── RequestWrapper.java
│ │ └── util
│ │ ├── FileReader.java
│ │ └── RandomDataGenerator.java
│ └── resources
│ ├── application.yml
│ └── requests
│ └── response1.xml
└── start_jar.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .project
3 | .settings/
4 | .classpath
5 | .idea
6 | **/*.iml
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mock Server
2 |
3 | Ready-to-use mock data server. You can quickly create HTTP API's which returns mock data.
4 |
5 |
6 |
7 | ## Usage (How to create HTTP API with mock data)
8 |
9 | There are 3 way to generate mock data:
10 |
11 | - ### 1 - RandomDataGenerator
12 |
13 | You can generate fake response with random data.
14 |
15 | See this DEMO method: ["MockServerDemoController class --> simpleFakeData() method"](./src/main/java/com/demo/mockservice/controller/MockServerDemoController.java#L29)
16 |
17 |
18 |
19 | - ### 2 - Dynamic Response
20 |
21 | The server will response you with the data which you send it in a special field.
22 |
23 | You don't even need to write a code for that.
24 |
25 | See this DEMO method: ["MockServerDemoController class --> dynamicResponseXML() method"](./src/main/java/com/demo/mockservice/controller/MockServerDemoController.java#L53)
26 |
27 |
28 |
29 | - ### 3 - Response from file
30 |
31 | You can return any file as a response.
32 |
33 | See this DEMO method: ["MockServerDemoController class --> resultFromFile() method"](./src/main/java/com/demo/mockservice/controller/MockServerDemoController.java#L41)
34 |
35 |
36 |
37 | ## HTTP Request and Response Logs
38 |
39 | You can check the request and response from:
40 |
41 | - Only latest 100 request and response:
42 |
43 | http://localhost:8088/actuator/httpexchanges
44 |
45 | - Server console logs
46 |
47 |
48 |
49 | ## Swagger Docs
50 |
51 | You may need to test mock services directly from HTML (Web) UI of Swagger.
52 |
53 | - Swagger HTML (Web) UI
54 |
55 | http://localhost:8088/swagger-ui.html
56 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 3.3.1
9 |
10 |
11 | jar
12 | com.demo
13 | mock_data_server
14 | 0.0.1-SNAPSHOT
15 |
16 | 21
17 | 21
18 | 21
19 |
20 |
21 |
22 | org.zalando
23 | logbook-spring-boot-starter
24 | 3.9.0
25 |
26 |
27 | org.springframework.boot
28 | spring-boot-starter-actuator
29 |
30 |
31 | com.fasterxml.jackson.dataformat
32 | jackson-dataformat-xml
33 |
34 |
35 | org.projectlombok
36 | lombok
37 | 1.18.34
38 | provided
39 |
40 |
41 | org.jeasy
42 | easy-random-core
43 | 5.0.0
44 |
45 |
46 | org.springdoc
47 | springdoc-openapi-starter-webmvc-ui
48 | 2.6.0
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-starter-web
53 |
54 |
55 |
56 |
57 |
58 | org.springframework.boot
59 | spring-boot-maven-plugin
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/App.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class App {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(App.class, args);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/config/FilterConfig.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.config;
2 |
3 | import com.demo.mockservice.filter.DynamicResponseFilter;
4 | import org.springframework.boot.web.servlet.FilterRegistrationBean;
5 | import org.springframework.context.annotation.Bean;
6 |
7 | public class FilterConfig {
8 |
9 | @Bean
10 | public FilterRegistrationBean automaticResponseGeneratorFilter() {
11 | FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
12 | registrationBean.setFilter(new DynamicResponseFilter());
13 | registrationBean.addUrlPatterns("/*");
14 | return registrationBean;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/config/HttpTraceActuatorConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.config;
2 |
3 |
4 | import org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository;
5 | import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 |
9 | @Configuration
10 | public class HttpTraceActuatorConfiguration {
11 |
12 | @Bean
13 | // HTTP Trace actuator
14 | public HttpExchangeRepository httpTraceRepository() {
15 | return new InMemoryHttpExchangeRepository();
16 | }
17 | }
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/config/TraceRequestFilter.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.config;
2 |
3 | import com.demo.mockservice.filter.DynamicResponseFilter;
4 | import org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository;
5 | import org.springframework.boot.actuate.web.exchanges.Include;
6 | import org.springframework.boot.actuate.web.exchanges.servlet.HttpExchangesFilter;
7 | import org.springframework.stereotype.Component;
8 |
9 | import jakarta.servlet.http.HttpServletRequest;
10 | import java.util.Arrays;
11 |
12 | @Component
13 | // HTTP Trace actuator filter (excludes the "swagger", "actuator" requests)
14 | public class TraceRequestFilter extends HttpExchangesFilter {
15 |
16 | public TraceRequestFilter(HttpExchangeRepository repository) {
17 | super(repository, Include.defaultIncludes());
18 | }
19 |
20 | @Override
21 | protected boolean shouldNotFilter(HttpServletRequest request) {
22 | if (request.getServletPath().equals("/")) {
23 | return true;
24 | }
25 | return Arrays.stream(DynamicResponseFilter.EXCLUDE_URL_LIST).anyMatch(request.getServletPath()::contains);
26 | }
27 | }
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/controller/MockServerDemoController.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.controller;
2 |
3 | import com.demo.mockservice.dto.BookSearchRequest;
4 | import com.demo.mockservice.dto.BookSearchResponse;
5 | import com.demo.mockservice.dto.DynamicResponseBookSearchRequest;
6 | import com.demo.mockservice.util.FileReader;
7 | import com.demo.mockservice.util.RandomDataGenerator;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.http.MediaType;
11 | import org.springframework.web.bind.annotation.RequestBody;
12 | import org.springframework.web.bind.annotation.RequestMapping;
13 | import org.springframework.web.bind.annotation.RequestMethod;
14 | import org.springframework.web.bind.annotation.RestController;
15 |
16 | /*
17 | This Controller is using to test the mock server itself.
18 | */
19 | @RequestMapping("/mockServerDemoController")
20 | @RestController
21 | public class MockServerDemoController {
22 |
23 | private final Logger logger = LoggerFactory.getLogger(MockServerDemoController.class);
24 |
25 | @RequestMapping(value = "/simpleFakeData",
26 | method = RequestMethod.POST,
27 | consumes = MediaType.APPLICATION_XML_VALUE,
28 | produces = MediaType.APPLICATION_XML_VALUE)
29 | public BookSearchResponse simpleFakeData(@RequestBody BookSearchRequest bookSearchRequest) {
30 |
31 | BookSearchResponse result = RandomDataGenerator.generateRandomData(BookSearchResponse.class);
32 | // Optional custom hard manipulations for "result" instance.
33 | result.setResponseId("This is hard manipulation example - from Java controller");
34 | return result;
35 | }
36 |
37 | @RequestMapping(value = "/resultFromFile",
38 | method = RequestMethod.POST,
39 | consumes = MediaType.APPLICATION_XML_VALUE,
40 | produces = MediaType.APPLICATION_XML_VALUE)
41 | public String resultFromFile(@RequestBody BookSearchRequest bookSearchRequest) {
42 |
43 | return FileReader.readFile("response1.xml");
44 | }
45 |
46 | /*
47 | You don't need below 2 methods. Below methods added here, so we can see them from Swagger-web-UI.
48 | */
49 | @RequestMapping(value = "/dynamicResponseXML",
50 | method = RequestMethod.POST,
51 | consumes = MediaType.APPLICATION_XML_VALUE,
52 | produces = MediaType.APPLICATION_XML_VALUE)
53 | public BookSearchResponse dynamicResponseXML(@RequestBody DynamicResponseBookSearchRequest bookSearchRequest) {
54 |
55 | return null;
56 | }
57 |
58 | @RequestMapping(value = "/dynamicResponseJSON",
59 | method = RequestMethod.POST,
60 | consumes = MediaType.APPLICATION_JSON_VALUE,
61 | produces = MediaType.APPLICATION_JSON_VALUE)
62 | public BookSearchResponse dynamicResponseJSON(@RequestBody DynamicResponseBookSearchRequest bookSearchRequest) {
63 |
64 | return null;
65 | }
66 | }
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/dto/Book.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.dto;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class Book {
7 | private String bookName;
8 | private String bookId;
9 | }
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/dto/BookSearchRequest.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.dto;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class BookSearchRequest {
7 | private int maxPage;
8 | private String bookName;
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/dto/BookSearchResponse.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.dto;
2 |
3 | import lombok.Data;
4 |
5 | import java.util.List;
6 |
7 | @Data
8 | public class BookSearchResponse {
9 | private int maxPage;
10 | private List results;
11 | private String responseId;
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/dto/DynamicResponseBookSearchRequest.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.dto;
2 |
3 | import io.swagger.v3.oas.annotations.media.Schema;
4 | import lombok.Data;
5 |
6 | @Data
7 | public class DynamicResponseBookSearchRequest {
8 |
9 | private int maxPage;
10 |
11 | private String bookName;
12 |
13 | @Schema(
14 | title = "This value will be return as response automatically from mock-server.",
15 | example = "{ 'customer': 'mehmet', 'book': { 'name': 'davinci code' } }")
16 | private String mockServerResponseData;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/filter/DynamicResponseFilter.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.filter;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.core.TreeNode;
5 | import com.fasterxml.jackson.databind.JsonNode;
6 | import com.fasterxml.jackson.databind.ObjectMapper;
7 | import com.fasterxml.jackson.dataformat.xml.XmlMapper;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.http.MediaType;
11 | import org.springframework.stereotype.Component;
12 |
13 | import jakarta.servlet.*;
14 | import jakarta.servlet.http.HttpServletRequest;
15 | import jakarta.servlet.http.HttpServletResponse;
16 | import java.io.IOException;
17 | import java.io.OutputStream;
18 | import java.nio.charset.StandardCharsets;
19 | import java.util.Arrays;
20 |
21 | @Component
22 | public class DynamicResponseFilter implements Filter {
23 |
24 | public static final String[] EXCLUDE_URL_LIST = new String[]{"csrf", "actuator", "v2/api-docs", "swagger", "springfox", "favicon.ico"};
25 | private static final String MOCK_RESPONSE_FIELD = "mockServerResponseData";
26 | private final Logger logger = LoggerFactory.getLogger(DynamicResponseFilter.class);
27 |
28 | @Override
29 | public void doFilter(ServletRequest request,
30 | ServletResponse response,
31 | FilterChain chain)
32 | throws IOException, ServletException {
33 |
34 | final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
35 | final String path = httpServletRequest.getRequestURI();
36 | if (!Arrays.stream(EXCLUDE_URL_LIST).anyMatch(path::contains) && !path.equals("/")) {
37 | if (request.getContentType() != null &&
38 | (request.getContentType().equals(MediaType.APPLICATION_XML_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE))) {
39 | parseResponseFromRequestAndFlushAsResponse(httpServletRequest, response, request.getContentType(), chain);
40 | return;
41 | }
42 | }
43 | chain.doFilter(httpServletRequest, response);
44 | }
45 |
46 | private void parseResponseFromRequestAndFlushAsResponse(
47 | final HttpServletRequest httpServletRequest,
48 | final ServletResponse response,
49 | final String contentType,
50 | final FilterChain chain)
51 | throws IOException, ServletException {
52 |
53 | final RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest);
54 | final String body = requestWrapper.getBody();
55 |
56 | String newResponse = null;
57 | try {
58 | if (contentType.equals(MediaType.APPLICATION_XML_VALUE)) {
59 | newResponse = parseResponseFromXML(body);
60 | } else if (contentType.equals(MediaType.APPLICATION_JSON_VALUE)) {
61 | newResponse = parseResponseFromJSON(body);
62 | }
63 | } catch (JsonProcessingException e) {
64 | e.printStackTrace();
65 | throw e;
66 | }
67 | if (newResponse == null) {
68 | // this case means that the request body does not contain the MOCK_RESPONSE_FIELD.
69 | chain.doFilter(requestWrapper, response);
70 | } else {
71 | sendResponse(newResponse, response, MediaType.APPLICATION_XML_VALUE);
72 | }
73 | }
74 |
75 | private String parseResponseFromJSON(final String json) throws JsonProcessingException {
76 | ObjectMapper objectMapper = new ObjectMapper();
77 | JsonNode rootJsonNode = objectMapper.readTree(json);
78 | JsonNode jsonNode = rootJsonNode.get(MOCK_RESPONSE_FIELD);
79 | if (jsonNode != null && !jsonNode.isMissingNode()) {
80 | return jsonNode.toPrettyString();
81 | }
82 | return null;
83 | }
84 |
85 | private String parseResponseFromXML(final String xml) throws JsonProcessingException {
86 | XmlMapper objectMapper = new XmlMapper();
87 | TreeNode rootXmlNode = objectMapper.readTree(xml);
88 | TreeNode xmlNode = rootXmlNode.get(MOCK_RESPONSE_FIELD);
89 | if (xmlNode != null && !xmlNode.isMissingNode()) {
90 | return xmlNode.toString();
91 | }
92 | return null;
93 | }
94 |
95 | private void sendResponse(final String responseBodyStr, final ServletResponse servletResponse, final String mediaType)
96 | throws IOException {
97 |
98 | final byte[] responseBody = responseBodyStr.getBytes(StandardCharsets.UTF_8);
99 | final HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
100 | httpServletResponse.setContentLength(responseBody.length);
101 | httpServletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
102 | httpServletResponse.setContentType(mediaType);
103 | final OutputStream outputStream = httpServletResponse.getOutputStream();
104 | outputStream.write(responseBody);
105 | outputStream.flush();
106 | outputStream.close();
107 | }
108 | }
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/filter/RequestWrapper.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.filter;
2 |
3 | import jakarta.servlet.ReadListener;
4 | import jakarta.servlet.ServletInputStream;
5 | import jakarta.servlet.http.HttpServletRequest;
6 | import jakarta.servlet.http.HttpServletRequestWrapper;
7 | import java.io.*;
8 |
9 | public class RequestWrapper extends HttpServletRequestWrapper {
10 | private final String body;
11 |
12 | public RequestWrapper(HttpServletRequest request) throws IOException {
13 | super(request); //So that other request method behave just like before
14 |
15 | StringBuilder stringBuilder = new StringBuilder();
16 | BufferedReader bufferedReader = null;
17 | try {
18 | InputStream inputStream = request.getInputStream();
19 | if (inputStream != null) {
20 | bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
21 | char[] charBuffer = new char[128];
22 | int bytesRead = -1;
23 | while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
24 | stringBuilder.append(charBuffer, 0, bytesRead);
25 | }
26 | } else {
27 | stringBuilder.append("");
28 | }
29 | } catch (IOException ex) {
30 | throw ex;
31 | } finally {
32 | if (bufferedReader != null) {
33 | try {
34 | bufferedReader.close();
35 | } catch (IOException ex) {
36 | throw ex;
37 | }
38 | }
39 | }
40 |
41 | body = stringBuilder.toString();
42 | }
43 |
44 | @Override
45 | public ServletInputStream getInputStream() throws IOException {
46 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
47 | ServletInputStream servletInputStream = new ServletInputStream() {
48 | @Override
49 | public boolean isFinished() {
50 | return false;
51 | }
52 |
53 | @Override
54 | public boolean isReady() {
55 | return true;
56 | }
57 |
58 | @Override
59 | public void setReadListener(ReadListener readListener) {
60 | }
61 |
62 | public int read() throws IOException {
63 | return byteArrayInputStream.read();
64 | }
65 | };
66 | return servletInputStream;
67 | }
68 |
69 | @Override
70 | public BufferedReader getReader() throws IOException {
71 | return new BufferedReader(new InputStreamReader(this.getInputStream()));
72 | }
73 |
74 | //Use this method to read the request body N times
75 | public String getBody() {
76 | return this.body;
77 | }
78 | }
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/util/FileReader.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.util;
2 |
3 | import com.demo.mockservice.App;
4 | import lombok.SneakyThrows;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.InputStream;
8 | import java.io.InputStreamReader;
9 |
10 | public class FileReader {
11 |
12 | @SneakyThrows
13 | public static String readFile(String fileName) {
14 |
15 | ClassLoader classLoader = App.class.getClassLoader();
16 | InputStream inputStream = classLoader.getResourceAsStream("requests/" + fileName);
17 |
18 | StringBuilder resultStringBuilder = new StringBuilder();
19 | try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
20 | String line;
21 | while ((line = br.readLine()) != null) {
22 | resultStringBuilder.append(line).append("\n");
23 | }
24 | }
25 | return resultStringBuilder.toString();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/demo/mockservice/util/RandomDataGenerator.java:
--------------------------------------------------------------------------------
1 | package com.demo.mockservice.util;
2 |
3 | import org.jeasy.random.EasyRandom;
4 |
5 | public class RandomDataGenerator {
6 |
7 | private static final EasyRandom easyRandom = new EasyRandom();
8 |
9 | public static T generateRandomData(Class clazz) {
10 | return easyRandom.nextObject(clazz);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8088
3 |
4 | spring:
5 | application:
6 | name: mock-server
7 |
8 | management:
9 | endpoints:
10 | web:
11 | exposure:
12 | include: "*" # enable all actuators which includes http-trace
13 |
14 | # log request and response to console output.
15 | logging.level.org.zalando.logbook: TRACE
16 |
17 | # below yml format should stay as is. Other yml formats does not work. possibly a bug.
18 | logbook.exclude[0]: "**/v2/api-docs"
19 | logbook.exclude[1]: "**/swagger-ui.html"
20 | logbook.exclude[2]: "**/webjars/**"
21 | logbook.exclude[3]: "**/swagger-resources/**"
22 | logbook.exclude[4]: "**/csrf"
23 | logbook.exclude[5]: "**/"
24 | logbook.exclude[6]: "/actuator**"
25 | logbook.exclude[7]: "/favicon.ico"
--------------------------------------------------------------------------------
/src/main/resources/requests/response1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | DaVinci Code
4 | This book is from file
5 |
6 |
--------------------------------------------------------------------------------
/start_jar.sh:
--------------------------------------------------------------------------------
1 | java -jar ./target/mock_data_server-0.0.1-SNAPSHOT.jar
--------------------------------------------------------------------------------