diff --git a/spring-core-2/README.md b/spring-core-2/README.md
index ec6eb91306..027c049163 100644
--- a/spring-core-2/README.md
+++ b/spring-core-2/README.md
@@ -16,4 +16,5 @@ This module contains articles about core Spring functionality
- [Spring Null-Safety Annotations](https://www.baeldung.com/spring-null-safety-annotations)
- [Using @Autowired in Abstract Classes](https://www.baeldung.com/spring-autowired-abstract-class)
- [Guide to the Spring BeanFactory](https://www.baeldung.com/spring-beanfactory)
-- More articles: [[<-- prev]](/spring-core)
+- [Read HttpServletRequest Multiple Times](https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times)
+- More articles: [[<-- prev]](/spring-core)
\ No newline at end of file
diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequest.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequest.java
new file mode 100644
index 0000000000..b6d4905653
--- /dev/null
+++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequest.java
@@ -0,0 +1,37 @@
+package org.baeldung.cachedrequest;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.springframework.util.StreamUtils;
+
+public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
+
+ private byte[] cachedBody;
+
+ public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
+ super(request);
+ InputStream requestInputStream = request.getInputStream();
+ this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ return new CachedBodyServletInputStream(this.cachedBody);
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ // Create a reader from cachedContent
+ // and return it
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
+ return new BufferedReader(new InputStreamReader(byteArrayInputStream));
+ }
+}
\ No newline at end of file
diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyServletInputStream.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyServletInputStream.java
new file mode 100644
index 0000000000..a18de30788
--- /dev/null
+++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/CachedBodyServletInputStream.java
@@ -0,0 +1,43 @@
+package org.baeldung.cachedrequest;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+
+public class CachedBodyServletInputStream extends ServletInputStream {
+
+ private InputStream cachedBodyInputStream;
+
+ public CachedBodyServletInputStream(byte[] cachedBody) {
+ this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
+ }
+
+ @Override
+ public boolean isFinished() {
+ try {
+ return cachedBodyInputStream.available() == 0;
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int read() throws IOException {
+ return cachedBodyInputStream.read();
+ }
+}
diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/ContentCachingFilter.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/ContentCachingFilter.java
new file mode 100644
index 0000000000..e3f3b7a060
--- /dev/null
+++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/ContentCachingFilter.java
@@ -0,0 +1,27 @@
+package org.baeldung.cachedrequest;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+@Order(value = Ordered.HIGHEST_PRECEDENCE)
+@Component
+@WebFilter(filterName = "ContentCachingFilter", urlPatterns = "/*")
+public class ContentCachingFilter extends OncePerRequestFilter {
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
+ System.out.println("IN ContentCachingFilter ");
+ CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(httpServletRequest);
+ filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse);
+ }
+}
diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/HttpRequestDemoConfig.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/HttpRequestDemoConfig.java
new file mode 100644
index 0000000000..9194bcf27c
--- /dev/null
+++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/HttpRequestDemoConfig.java
@@ -0,0 +1,17 @@
+package org.baeldung.cachedrequest;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+/**
+ * To initialize the WebApplication, Please see
+ * {@link org.baeldung.spring.config.MainWebAppInitializer}
+ */
+
+@EnableWebMvc
+@Configuration
+@ComponentScan(basePackages = "org.baeldung.cachedrequest")
+public class HttpRequestDemoConfig implements WebMvcConfigurer {
+
+}
\ No newline at end of file
diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/Person.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/Person.java
new file mode 100644
index 0000000000..594b6f2360
--- /dev/null
+++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/Person.java
@@ -0,0 +1,47 @@
+package org.baeldung.cachedrequest;
+
+public class Person {
+ private String firstName;
+
+ private String lastName;
+
+ private int age;
+
+ public Person() {
+ }
+
+ public Person(String firstName, String lastName, int age) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.age = age;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ @Override
+ public String toString() {
+ return "Person{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", age=" + age + '}';
+ }
+}
diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/PersonController.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/PersonController.java
new file mode 100644
index 0000000000..6f241b3fb3
--- /dev/null
+++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/PersonController.java
@@ -0,0 +1,26 @@
+package org.baeldung.cachedrequest;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class PersonController {
+
+ @PostMapping(value = "/person")
+ @ResponseStatus(value = HttpStatus.NO_CONTENT)
+ public void printPerson(@RequestBody Person person) {
+
+ System.out.println("In Demo Controller. Person " + "is : " + person);
+ }
+
+ @GetMapping(value = "/person")
+ @ResponseStatus(value = HttpStatus.NO_CONTENT)
+ public void getPerson() {
+
+ System.out.println("In Demo Controller get method.");
+ }
+}
\ No newline at end of file
diff --git a/spring-core-2/src/main/java/org/baeldung/cachedrequest/PrintRequestContentFilter.java b/spring-core-2/src/main/java/org/baeldung/cachedrequest/PrintRequestContentFilter.java
new file mode 100644
index 0000000000..55455f590f
--- /dev/null
+++ b/spring-core-2/src/main/java/org/baeldung/cachedrequest/PrintRequestContentFilter.java
@@ -0,0 +1,31 @@
+package org.baeldung.cachedrequest;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StreamUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+@Order(Ordered.LOWEST_PRECEDENCE)
+@Component
+@WebFilter(filterName = "printRequestContentFilter", urlPatterns = "/*")
+public class PrintRequestContentFilter extends OncePerRequestFilter {
+ @Override
+ protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
+ System.out.println("IN PrintRequestContentFilter ");
+ InputStream inputStream = httpServletRequest.getInputStream();
+ byte[] body = StreamUtils.copyToByteArray(inputStream);
+ System.out.println("In PrintRequestContentFilter. Request body is: " + new String(body));
+ filterChain.doFilter(httpServletRequest, httpServletResponse);
+ }
+}
diff --git a/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequestUnitTest.java b/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequestUnitTest.java
new file mode 100644
index 0000000000..1fe9dd4c82
--- /dev/null
+++ b/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyHttpServletRequestUnitTest.java
@@ -0,0 +1,62 @@
+package org.baeldung.cachedrequest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.util.StreamUtils;
+
+import junit.framework.TestCase;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CachedBodyHttpServletRequestUnitTest extends TestCase {
+
+ private CachedBodyServletInputStream servletInputStream;
+
+ @After
+ public void cleanUp() throws IOException {
+ if (null != servletInputStream) {
+ servletInputStream.close();
+ }
+ }
+
+ @Test
+ public void testGivenHttpServletRequestWithBody_whenCalledGetInputStream_ThenGetsServletInputStreamWithSameBody() throws IOException {
+ // Given
+ byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes();
+ MockHttpServletRequest mockeddHttpServletRequest = new MockHttpServletRequest();
+ mockeddHttpServletRequest.setContent(cachedBody);
+ CachedBodyHttpServletRequest request = new CachedBodyHttpServletRequest(mockeddHttpServletRequest);
+
+ // when
+ InputStream inputStream = request.getInputStream();
+
+ // then
+ assertEquals(new String(cachedBody), new String(StreamUtils.copyToByteArray(inputStream)));
+ }
+
+ @Test
+ public void testGivenHttpServletRequestWithBody_whenCalledGetReader_ThenGetBufferedReaderWithSameBody() throws IOException {
+ // Given
+ byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes();
+ MockHttpServletRequest mockeddHttpServletRequest = new MockHttpServletRequest();
+ mockeddHttpServletRequest.setContent(cachedBody);
+ CachedBodyHttpServletRequest request = new CachedBodyHttpServletRequest(mockeddHttpServletRequest);
+
+ // when
+ BufferedReader bufferedReader = request.getReader();
+
+ // then
+ String line = "";
+ StringBuilder builder = new StringBuilder();
+ while ((line = bufferedReader.readLine()) != null) {
+ builder.append(line);
+ }
+ assertEquals(new String(cachedBody), builder.toString());
+ }
+}
diff --git a/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyServletInputStreamUnitTest.java b/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyServletInputStreamUnitTest.java
new file mode 100644
index 0000000000..d7000d91ee
--- /dev/null
+++ b/spring-core-2/src/test/java/org/baeldung/cachedrequest/CachedBodyServletInputStreamUnitTest.java
@@ -0,0 +1,98 @@
+package org.baeldung.cachedrequest;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import javax.servlet.ReadListener;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.util.StreamUtils;
+
+import junit.framework.TestCase;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CachedBodyServletInputStreamUnitTest extends TestCase {
+
+ private CachedBodyServletInputStream servletInputStream;
+
+ @After
+ public void cleanUp() throws IOException {
+ if (null != servletInputStream) {
+ servletInputStream.close();
+ }
+ }
+
+ @Test
+ public void testGivenServletInputStreamCreated_whenCalledisFinished_Thenfalse() {
+ // Given
+ byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes();
+ servletInputStream = new CachedBodyServletInputStream(cachedBody);
+
+ // when
+ boolean finished = servletInputStream.isFinished();
+
+ // then
+ assertFalse(finished);
+ }
+
+ @Test
+ public void testGivenServletInputStreamCreatedAndBodyRead_whenCalledisFinished_ThenTrue() throws IOException {
+ // Given
+ byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes();
+ servletInputStream = new CachedBodyServletInputStream(cachedBody);
+ StreamUtils.copyToByteArray(servletInputStream);
+
+ // when
+ boolean finished = servletInputStream.isFinished();
+
+ // then
+ assertTrue(finished);
+ }
+
+ @Test
+ public void testGivenServletInputStreamCreatedAndBodyRead_whenCalledIsReady_ThenTrue() throws IOException {
+ // Given
+ byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes();
+ servletInputStream = new CachedBodyServletInputStream(cachedBody);
+
+ // when
+ boolean ready = servletInputStream.isReady();
+
+ // then
+ assertTrue(ready);
+ }
+
+ @Test
+ public void testGivenServletInputStreamCreated_whenCalledIsRead_ThenReturnsBody() throws IOException {
+ // Given
+ byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes();
+ servletInputStream = new CachedBodyServletInputStream(cachedBody);
+
+ // when
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ int len = 0;
+ byte[] buffer = new byte[1024];
+ while ((len = servletInputStream.read(buffer)) != -1) {
+ byteArrayOutputStream.write(buffer, 0, len);
+ }
+
+ // then
+ assertEquals(new String(cachedBody), new String(byteArrayOutputStream.toByteArray()));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testGivenServletInputStreamCreated_whenCalledIsRead_ThenThrowsException() throws IOException {
+ // Given
+ byte[] cachedBody = "{\"firstName\" :\"abc\",\"lastName\" : \"xyz\",\"age\" : 30\"}".getBytes();
+ servletInputStream = new CachedBodyServletInputStream(cachedBody);
+
+ // when
+ servletInputStream.setReadListener(Mockito.mock(ReadListener.class));
+
+ }
+
+}
diff --git a/spring-core-2/src/test/java/org/baeldung/cachedrequest/ContentCachingFilterUnitTest.java b/spring-core-2/src/test/java/org/baeldung/cachedrequest/ContentCachingFilterUnitTest.java
new file mode 100644
index 0000000000..057f1ac627
--- /dev/null
+++ b/spring-core-2/src/test/java/org/baeldung/cachedrequest/ContentCachingFilterUnitTest.java
@@ -0,0 +1,39 @@
+package org.baeldung.cachedrequest;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import junit.framework.TestCase;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ContentCachingFilterUnitTest extends TestCase {
+
+ @InjectMocks
+ private ContentCachingFilter filterToTest;
+
+ @Test
+ public void testGivenHttpRequest_WhenDoFilter_thenCreatesRequestWrapperObject() throws IOException, ServletException {
+ // Given
+ MockHttpServletRequest mockedRequest = new MockHttpServletRequest();
+ MockHttpServletResponse mockedResponse = new MockHttpServletResponse();
+ FilterChain mockedFilterChain = Mockito.mock(FilterChain.class);
+
+ // when
+ filterToTest.doFilter(mockedRequest, mockedResponse, mockedFilterChain);
+
+ // then
+ Mockito.verify(mockedFilterChain, Mockito.times(1))
+ .doFilter(Mockito.any(CachedBodyHttpServletRequest.class), Mockito.any(MockHttpServletResponse.class));
+ }
+
+}
diff --git a/spring-core-2/src/test/java/org/baeldung/cachedrequest/PersonControllerIntegrationTest.java b/spring-core-2/src/test/java/org/baeldung/cachedrequest/PersonControllerIntegrationTest.java
new file mode 100644
index 0000000000..77d6a816e3
--- /dev/null
+++ b/spring-core-2/src/test/java/org/baeldung/cachedrequest/PersonControllerIntegrationTest.java
@@ -0,0 +1,46 @@
+package org.baeldung.cachedrequest;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.io.IOException;
+
+import javax.print.attribute.PrintRequestAttribute;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+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.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { HttpRequestDemoConfig.class, ContentCachingFilter.class, PrintRequestAttribute.class })
+@AutoConfigureMockMvc
+public class PersonControllerIntegrationTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ ObjectMapper objectMapper = new ObjectMapper();
+
+ @Test
+ public void whenValidInput_thenCreateBook() throws IOException, Exception {
+ // assign - given
+ Person book = new Person("sumit", "abc", 100);
+
+ // act - when
+ ResultActions result = mockMvc.perform(post("/person").accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(book)));
+
+ // assert - then
+ result.andExpect(status().isNoContent());
+ }
+
+}
diff --git a/spring-core-2/src/test/java/org/baeldung/cachedrequest/PrintRequestContentFilterUnitTest.java b/spring-core-2/src/test/java/org/baeldung/cachedrequest/PrintRequestContentFilterUnitTest.java
new file mode 100644
index 0000000000..ca2d5c600b
--- /dev/null
+++ b/spring-core-2/src/test/java/org/baeldung/cachedrequest/PrintRequestContentFilterUnitTest.java
@@ -0,0 +1,40 @@
+package org.baeldung.cachedrequest;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import junit.framework.TestCase;
+
+@RunWith(MockitoJUnitRunner.class)
+public class PrintRequestContentFilterUnitTest extends TestCase {
+
+ @InjectMocks
+ private PrintRequestContentFilter filterToTest;
+
+ @Test
+ public void testGivenHttpRequest_WhenDoFilter_thenReadsBody() throws IOException, ServletException {
+ // Given
+ MockHttpServletRequest mockedRequest = new MockHttpServletRequest();
+ MockHttpServletResponse mockedResponse = new MockHttpServletResponse();
+ FilterChain mockedFilterChain = Mockito.mock(FilterChain.class);
+ CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(mockedRequest);
+
+ // when
+ filterToTest.doFilter(cachedBodyHttpServletRequest, mockedResponse, mockedFilterChain);
+
+ // then
+ Mockito.verify(mockedFilterChain, Mockito.times(1))
+ .doFilter(cachedBodyHttpServletRequest, mockedResponse);
+ }
+
+}
diff --git a/testing-modules/gatling/Jenkinsfile b/testing-modules/gatling/Jenkinsfile
new file mode 100644
index 0000000000..0786788406
--- /dev/null
+++ b/testing-modules/gatling/Jenkinsfile
@@ -0,0 +1,20 @@
+pipeline {
+ agent any
+ stages {
+ stage("Build Maven") {
+ steps {
+ sh 'mvn -B clean package'
+ }
+ }
+ stage("Run Gatling") {
+ steps {
+ sh 'mvn gatling:test'
+ }
+ post {
+ always {
+ gatlingArchive()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/testing-modules/gatling/pom.xml b/testing-modules/gatling/pom.xml
index 37693ebfee..d105cc8b3e 100644
--- a/testing-modules/gatling/pom.xml
+++ b/testing-modules/gatling/pom.xml
@@ -1,13 +1,13 @@
- 4.0.0
- org.baeldung
- gatling
- 1.0-SNAPSHOT
- gatling
-
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
+ org.baeldung
+ gatling
+ 1.0-SNAPSHOT
+ gatling
+
com.baeldung
parent-modules
@@ -15,122 +15,106 @@
../../
-
-
-
- io.gatling
- gatling-app
- ${gatling.version}
-
-
- io.gatling
- gatling-recorder
- ${gatling.version}
-
-
- io.gatling.highcharts
- gatling-charts-highcharts
- ${gatling.version}
-
-
- org.scala-lang
- scala-library
- ${scala.version}
-
-
-
-
-
-
- io.gatling.highcharts
- gatling-charts-highcharts
-
-
- io.gatling
- gatling-app
-
-
- io.gatling
- gatling-recorder
-
-
- org.scala-lang
- scala-library
-
-
-
- src/test/scala
-
-
-
- net.alchim31.maven
- scala-maven-plugin
- ${scala-maven-plugin.version}
-
-
-
-
-
- net.alchim31.maven
- scala-maven-plugin
-
-
-
- testCompile
-
-
-
-
- -Ydelambdafy:method
- -target:jvm-1.8
- -deprecation
- -feature
- -unchecked
- -language:implicitConversions
- -language:postfixOps
-
-
-
-
-
-
-
-
-
-
- simulation
-
-
-
- io.gatling
- gatling-maven-plugin
- ${gatling-maven-plugin.version}
-
-
- test
-
- execute
-
-
- true
-
-
-
-
-
-
-
-
+
+
+
+ io.gatling
+ gatling-app
+ ${gatling.version}
+
+
+ io.gatling
+ gatling-recorder
+ ${gatling.version}
+
+
+ io.gatling.highcharts
+ gatling-charts-highcharts
+ ${gatling.version}
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+
+
+
-
- 1.8
- 1.8
- UTF-8
- 2.12.6
- 2.3.1
- 3.2.2
- 2.2.4
-
+
+
+ io.gatling.highcharts
+ gatling-charts-highcharts
+
+
+ io.gatling
+ gatling-app
+
+
+ io.gatling
+ gatling-recorder
+
+
+ org.scala-lang
+ scala-library
+
+
+
+
+ src/test/scala
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ ${scala-maven-plugin.version}
+
+
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+
+
+
+ testCompile
+
+
+
+
+ -Ydelambdafy:method
+ -target:jvm-1.8
+ -deprecation
+ -feature
+ -unchecked
+ -language:implicitConversions
+ -language:postfixOps
+
+
+
+
+
+
+ io.gatling
+ gatling-maven-plugin
+ ${gatling-maven-plugin.version}
+
+ org.baeldung.RecordedSimulation
+
+
+
+
+
+
+
+ 1.8
+ 1.8
+ UTF-8
+ 2.12.6
+ 3.3.1
+ 4.3.0
+ 3.0.4
+
diff --git a/testing-modules/gatling/src/test/scala/Engine.scala b/testing-modules/gatling/src/test/scala/Engine.scala
index c2884fc218..a34d3eaf60 100644
--- a/testing-modules/gatling/src/test/scala/Engine.scala
+++ b/testing-modules/gatling/src/test/scala/Engine.scala
@@ -3,11 +3,10 @@ import io.gatling.core.config.GatlingPropertiesBuilder
object Engine extends App {
- val props = new GatlingPropertiesBuilder
- props.dataDirectory(IDEPathHelper.dataDirectory.toString)
- props.resultsDirectory(IDEPathHelper.resultsDirectory.toString)
- props.bodiesDirectory(IDEPathHelper.bodiesDirectory.toString)
- props.binariesDirectory(IDEPathHelper.mavenBinariesDirectory.toString)
+ val props = new GatlingPropertiesBuilder()
+ .resourcesDirectory(IDEPathHelper.resourcesDirectory.toString)
+ .resultsDirectory(IDEPathHelper.resultsDirectory.toString)
+ .binariesDirectory(IDEPathHelper.mavenBinariesDirectory.toString)
- Gatling.fromMap(props.build)
+ Gatling.fromMap(props.build)
}
diff --git a/testing-modules/gatling/src/test/scala/IDEPathHelper.scala b/testing-modules/gatling/src/test/scala/IDEPathHelper.scala
index 9fb1d7d5c8..6aef6707b2 100644
--- a/testing-modules/gatling/src/test/scala/IDEPathHelper.scala
+++ b/testing-modules/gatling/src/test/scala/IDEPathHelper.scala
@@ -4,7 +4,7 @@ import io.gatling.commons.util.PathHelper._
object IDEPathHelper {
- val gatlingConfUrl: Path = getClass.getClassLoader.getResource("gatling.conf").toURI
+ val gatlingConfUrl: Path = getClass.getClassLoader.getResource("gatling.conf")
val projectRootDir = gatlingConfUrl.ancestor(3)
val mavenSourcesDirectory = projectRootDir / "src" / "test" / "scala"
@@ -12,11 +12,8 @@ object IDEPathHelper {
val mavenTargetDirectory = projectRootDir / "target"
val mavenBinariesDirectory = mavenTargetDirectory / "test-classes"
- val dataDirectory = mavenResourcesDirectory / "data"
- val bodiesDirectory = mavenResourcesDirectory / "bodies"
-
- val recorderOutputDirectory = mavenSourcesDirectory
+ val resourcesDirectory = mavenResourcesDirectory
+ val recorderSimulationsDirectory = mavenSourcesDirectory
val resultsDirectory = mavenTargetDirectory / "gatling"
-
val recorderConfigFile = mavenResourcesDirectory / "recorder.conf"
}
diff --git a/testing-modules/gatling/src/test/scala/Recorder.scala b/testing-modules/gatling/src/test/scala/Recorder.scala
index 9c38e52f12..187f566aac 100644
--- a/testing-modules/gatling/src/test/scala/Recorder.scala
+++ b/testing-modules/gatling/src/test/scala/Recorder.scala
@@ -3,10 +3,10 @@ import io.gatling.recorder.config.RecorderPropertiesBuilder
object Recorder extends App {
- val props = new RecorderPropertiesBuilder
- props.simulationOutputFolder(IDEPathHelper.recorderOutputDirectory.toString)
- props.simulationPackage("org.baeldung")
- props.bodiesFolder(IDEPathHelper.bodiesDirectory.toString)
+ val props = new RecorderPropertiesBuilder()
+ .simulationsFolder(IDEPathHelper.recorderSimulationsDirectory.toString)
+ .simulationPackage("org.baeldung")
+ .resourcesFolder(IDEPathHelper.resourcesDirectory.toString)
GatlingRecorder.fromMap(props.build, Some(IDEPathHelper.recorderConfigFile))
}
diff --git a/testing-modules/gatling/src/test/scala/org/baeldung/RecordedSimulation.scala b/testing-modules/gatling/src/test/scala/org/baeldung/RecordedSimulation.scala
index dece393478..9902f91e0f 100644
--- a/testing-modules/gatling/src/test/scala/org/baeldung/RecordedSimulation.scala
+++ b/testing-modules/gatling/src/test/scala/org/baeldung/RecordedSimulation.scala
@@ -9,7 +9,7 @@ import io.gatling.jdbc.Predef._
class RecordedSimulation extends Simulation {
val httpProtocol = http
- .baseURL("http://computer-database.gatling.io")
+ .baseUrl("http://computer-database.gatling.io")
.inferHtmlResources(BlackList(""".*\.css""", """.*\.js""", """.*\.ico"""), WhiteList())
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.acceptEncodingHeader("gzip, deflate")
diff --git a/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/fluentapi/Pizza.java b/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/fluentapi/Pizza.java
new file mode 100644
index 0000000000..0a37a2cbbd
--- /dev/null
+++ b/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/fluentapi/Pizza.java
@@ -0,0 +1,102 @@
+package com.baeldung.mockito.fluentapi;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Pizza {
+
+ public enum PizzaSize {
+ LARGE, MEDIUM, SMALL;
+ }
+
+ private String name;
+ private PizzaSize size;
+ private List toppings;
+ private boolean stuffedCrust;
+ private boolean collect;
+ private Integer discount;
+
+ private Pizza(PizzaBuilder builder) {
+ this.name = builder.name;
+ this.size = builder.size;
+ this.toppings = builder.toppings;
+ this.stuffedCrust = builder.stuffedCrust;
+ this.collect = builder.collect;
+ this.discount = builder.discount;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public PizzaSize getSize() {
+ return size;
+ }
+
+ public List getToppings() {
+ return toppings;
+ }
+
+ public boolean isStuffedCrust() {
+ return stuffedCrust;
+ }
+
+ public boolean isCollecting() {
+ return collect;
+ }
+
+ public Integer getDiscount() {
+ return discount;
+ }
+
+ public static class PizzaBuilder {
+ private String name;
+ private PizzaSize size;
+
+ private List toppings;
+ private boolean stuffedCrust;
+ private boolean collect;
+ private Integer discount = null;
+
+ public PizzaBuilder() {
+ }
+
+ public PizzaBuilder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public PizzaBuilder size(PizzaSize size) {
+ this.size = size;
+ return this;
+ }
+
+ public PizzaBuilder withExtraTopping(String extraTopping) {
+ if (this.toppings == null) {
+ toppings = new ArrayList<>();
+ }
+ this.toppings.add(extraTopping);
+ return this;
+ }
+
+ public PizzaBuilder withStuffedCrust(boolean stuffedCrust) {
+ this.stuffedCrust = stuffedCrust;
+ return this;
+ }
+
+ public PizzaBuilder willCollect(boolean collect) {
+ this.collect = collect;
+ return this;
+ }
+
+ public PizzaBuilder applyDiscount(Integer discount) {
+ this.discount = discount;
+ return this;
+ }
+
+ public Pizza build() {
+ return new Pizza(this);
+ }
+ }
+
+}
diff --git a/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/fluentapi/PizzaService.java b/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/fluentapi/PizzaService.java
new file mode 100644
index 0000000000..0f9d5c6b18
--- /dev/null
+++ b/testing-modules/mockito-2/src/main/java/com/baeldung/mockito/fluentapi/PizzaService.java
@@ -0,0 +1,23 @@
+package com.baeldung.mockito.fluentapi;
+
+import com.baeldung.mockito.fluentapi.Pizza.PizzaSize;
+
+public class PizzaService {
+
+ private Pizza.PizzaBuilder builder;
+
+ public PizzaService(Pizza.PizzaBuilder builder) {
+ this.builder = builder;
+ }
+
+ public Pizza orderHouseSpecial() {
+ return builder.name("Special")
+ .size(PizzaSize.LARGE)
+ .withExtraTopping("Mushrooms")
+ .withStuffedCrust(true)
+ .withExtraTopping("Chilli")
+ .willCollect(true)
+ .applyDiscount(20)
+ .build();
+ }
+}
diff --git a/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/fluentapi/PizzaServiceUnitTest.java b/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/fluentapi/PizzaServiceUnitTest.java
new file mode 100644
index 0000000000..b5dd10b1d4
--- /dev/null
+++ b/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/fluentapi/PizzaServiceUnitTest.java
@@ -0,0 +1,88 @@
+package com.baeldung.mockito.fluentapi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import com.baeldung.mockito.fluentapi.Pizza.PizzaBuilder;
+import com.baeldung.mockito.fluentapi.Pizza.PizzaSize;
+
+public class PizzaServiceUnitTest {
+
+ @Mock
+ private Pizza expectedPizza;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PizzaBuilder anotherbuilder;
+
+ @Captor
+ private ArgumentCaptor stringCaptor;
+ @Captor
+ private ArgumentCaptor sizeCaptor;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void givenTraditonalMocking_whenServiceInvoked_thenPizzaIsBuilt() {
+ PizzaBuilder nameBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
+ PizzaBuilder sizeBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
+ PizzaBuilder firstToppingBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
+ PizzaBuilder secondToppingBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
+ PizzaBuilder stuffedBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
+ PizzaBuilder willCollectBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
+ PizzaBuilder discountBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
+
+ PizzaBuilder builder = Mockito.mock(Pizza.PizzaBuilder.class);
+ when(builder.name(anyString())).thenReturn(nameBuilder);
+ when(nameBuilder.size(any(Pizza.PizzaSize.class))).thenReturn(sizeBuilder);
+ when(sizeBuilder.withExtraTopping(anyString())).thenReturn(firstToppingBuilder);
+ when(firstToppingBuilder.withStuffedCrust(anyBoolean())).thenReturn(stuffedBuilder);
+ when(stuffedBuilder.withExtraTopping(anyString())).thenReturn(secondToppingBuilder);
+ when(secondToppingBuilder.willCollect(anyBoolean())).thenReturn(willCollectBuilder);
+ when(willCollectBuilder.applyDiscount(anyInt())).thenReturn(discountBuilder);
+ when(discountBuilder.build()).thenReturn(expectedPizza);
+
+ PizzaService service = new PizzaService(builder);
+ assertEquals("Expected Pizza", expectedPizza, service.orderHouseSpecial());
+
+ verify(builder).name(stringCaptor.capture());
+ assertEquals("Pizza name: ", "Special", stringCaptor.getValue());
+
+ verify(nameBuilder).size(sizeCaptor.capture());
+ assertEquals("Pizza size: ", PizzaSize.LARGE, sizeCaptor.getValue());
+
+ }
+
+ @Test
+ public void givenDeepStubs_whenServiceInvoked_thenPizzaIsBuilt() {
+ Mockito.when(anotherbuilder.name(anyString())
+ .size(any(Pizza.PizzaSize.class))
+ .withExtraTopping(anyString())
+ .withStuffedCrust(anyBoolean())
+ .withExtraTopping(anyString())
+ .willCollect(anyBoolean())
+ .applyDiscount(anyInt())
+ .build())
+ .thenReturn(expectedPizza);
+
+ PizzaService service = new PizzaService(anotherbuilder);
+ assertEquals("Expected Pizza", expectedPizza, service.orderHouseSpecial());
+ }
+
+}
diff --git a/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/fluentapi/PizzaUnitTest.java b/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/fluentapi/PizzaUnitTest.java
new file mode 100644
index 0000000000..1486cb7f2f
--- /dev/null
+++ b/testing-modules/mockito-2/src/test/java/com/baeldung/mockito/fluentapi/PizzaUnitTest.java
@@ -0,0 +1,32 @@
+package com.baeldung.mockito.fluentapi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.baeldung.mockito.fluentapi.Pizza.PizzaSize;
+
+public class PizzaUnitTest {
+
+ @Test
+ public void givenFluentPizzaApi_whenBuilt_thenPizzaHasCorrectAttributes() {
+ Pizza pizza = new Pizza.PizzaBuilder()
+ .name("Margherita")
+ .size(PizzaSize.LARGE)
+ .withExtraTopping("Mushroom")
+ .withStuffedCrust(false)
+ .willCollect(true)
+ .applyDiscount(20)
+ .build();
+
+ assertEquals("Pizza name: ", "Margherita", pizza.getName());
+ assertEquals("Pizza size: ", PizzaSize.LARGE, pizza.getSize());
+ assertEquals("Extra toppings: ", "Mushroom", pizza.getToppings()
+ .get(0));
+ assertFalse("Has stuffed crust: ", pizza.isStuffedCrust());
+ assertTrue("Will collect: ", pizza.isCollecting());
+ assertEquals("Discounts: ", Integer.valueOf(20), pizza.getDiscount());
+ }
+}