Merge branch 'master' into JAVA-3524

This commit is contained in:
Sampada
2020-12-29 23:52:29 +05:30
committed by GitHub
60 changed files with 5 additions and 8 deletions
+2
View File
@@ -23,8 +23,10 @@
<module>spring-mvc-forms-thymeleaf</module>
<module>spring-mvc-java</module>
<module>spring-mvc-java-2</module>
<module>spring-rest-http</module>
<module>spring-thymeleaf</module>
<module>spring-thymeleaf-2</module>
<module>spring-thymeleaf-3</module>
</modules>
</project>
@@ -0,0 +1,17 @@
## Spring REST HTTP
This module contains articles about HTTP in REST APIs with Spring
### The Course
The "REST With Spring" Classes: http://bit.ly/restwithspring
### Relevant Articles:
- [Guide to UriComponentsBuilder in Spring](https://www.baeldung.com/spring-uricomponentsbuilder)
- [How to Set a Header on a Response with Spring 5](https://www.baeldung.com/spring-response-header) - The tests contained for this article rely on the sample application within the [spring-resttemplate](/spring-resttemplate) module
- [Returning Custom Status Codes from Spring Controllers](https://www.baeldung.com/spring-mvc-controller-custom-http-status-code)
- [Spring RequestMapping](https://www.baeldung.com/spring-requestmapping)
- [Guide to DeferredResult in Spring](https://www.baeldung.com/spring-deferred-result)
- [Using JSON Patch in Spring REST APIs](https://www.baeldung.com/spring-rest-json-patch)
- [OpenAPI JSON Objects as Query Parameters](https://www.baeldung.com/openapi-json-query-parameters)
- [Dates in OpenAPI Files](https://www.baeldung.com/openapi-dates)
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 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">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-rest-http</artifactId>
<version>0.1-SNAPSHOT</version>
<name>spring-rest-http</name>
<packaging>war</packaging>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-2</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>${xstream.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.github.java-json-tools</groupId>
<artifactId>json-patch</artifactId>
<version>${jsonpatch.version}</version>
</dependency>
</dependencies>
<properties>
<xstream.version>1.4.9</xstream.version>
<jsonpatch.version>1.12</jsonpatch.version>
</properties>
</project>
@@ -0,0 +1,12 @@
package com.baeldung;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CustomerSpringBootRestApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerSpringBootRestApplication.class, args);
}
}
@@ -0,0 +1,51 @@
package com.baeldung.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.oxm.xstream.XStreamMarshaller;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.text.SimpleDateFormat;
import java.util.List;
/*
* Please note that main web configuration is in src/main/webapp/WEB-INF/api-servlet.xml
*/
@Configuration
@EnableWebMvc
@ComponentScan({ "com.baeldung.web.controller.status", "com.baeldung.requestmapping" })
public class MvcConfig implements WebMvcConfigurer {
public MvcConfig() {
super();
}
//
@Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> messageConverters) {
final Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true)
.dateFormat(new SimpleDateFormat("dd-MM-yyyy hh:mm"));
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
@@ -0,0 +1,76 @@
package com.baeldung.controllers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.concurrent.ForkJoinPool;
@RestController
public class DeferredResultController {
private final static Logger LOG = LoggerFactory.getLogger(DeferredResultController.class);
@GetMapping("/async-deferredresult")
public DeferredResult<ResponseEntity<?>> handleReqDefResult(Model model) {
LOG.info("Received async-deferredresult request");
DeferredResult<ResponseEntity<?>> output = new DeferredResult<>();
ForkJoinPool.commonPool().submit(() -> {
LOG.info("Processing in separate thread");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
}
output.setResult(ResponseEntity.ok("ok"));
});
LOG.info("servlet thread freed");
return output;
}
public DeferredResult<ResponseEntity<?>> handleReqWithTimeouts(Model model) {
LOG.info("Received async request with a configured timeout");
DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>(500l);
deferredResult.onTimeout(new Runnable() {
@Override
public void run() {
deferredResult.setErrorResult(
ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body("Request timeout occurred."));
}
});
ForkJoinPool.commonPool().submit(() -> {
LOG.info("Processing in separate thread");
try {
Thread.sleep(600l);
deferredResult.setResult(ResponseEntity.ok("ok"));
} catch (InterruptedException e) {
LOG.info("Request processing interrupted");
deferredResult.setErrorResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("INTERNAL_SERVER_ERROR occurred."));
}
});
LOG.info("servlet thread freed");
return deferredResult;
}
public DeferredResult<ResponseEntity<?>> handleAsyncFailedRequest(Model model) {
DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>();
ForkJoinPool.commonPool().submit(() -> {
try {
// Exception occurred in processing
throw new Exception();
} catch (Exception e) {
LOG.info("Request processing failed");
deferredResult.setErrorResult(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("INTERNAL_SERVER_ERROR occurred."));
}
});
return deferredResult;
}
}
@@ -0,0 +1,79 @@
package com.baeldung.model;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class Customer {
private String id;
private String telephone;
private List<String> favorites;
private Map<String, Boolean> communicationPreferences;
public Customer() {
}
public Customer(String id, String telephone, List<String> favorites, Map<String, Boolean> communicationPreferences) {
this(telephone, favorites, communicationPreferences);
this.id = id;
}
public Customer(String telephone, List<String> favorites, Map<String, Boolean> communicationPreferences) {
this.telephone = telephone;
this.favorites = favorites;
this.communicationPreferences = communicationPreferences;
}
public static Customer fromCustomer(Customer customer) {
return new Customer(customer.getId(), customer.getTelephone(), customer.getFavorites(), customer.getCommunicationPreferences());
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public Map<String, Boolean> getCommunicationPreferences() {
return communicationPreferences;
}
public void setCommunicationPreferences(Map<String, Boolean> communicationPreferences) {
this.communicationPreferences = communicationPreferences;
}
public List<String> getFavorites() {
return favorites;
}
public void setFavorites(List<String> favorites) {
this.favorites = favorites;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Customer)) {
return false;
}
Customer customer = (Customer) o;
return Objects.equals(id, customer.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
@@ -0,0 +1,47 @@
package com.baeldung.requestmapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(value = "/ex")
public class BarMappingExamplesController {
public BarMappingExamplesController() {
super();
}
// API
// with @RequestParam
@RequestMapping(value = "/bars")
@ResponseBody
public String getBarBySimplePathWithRequestParam(@RequestParam("id") final long id) {
return "Get a specific Bar with id=" + id;
}
@RequestMapping(value = "/bars", params = "id")
@ResponseBody
public String getBarBySimplePathWithExplicitRequestParam(@RequestParam("id") final long id) {
return "Get a specific Bar with id=" + id;
}
@RequestMapping(value = "/bars", params = { "id", "second" })
@ResponseBody
public String getBarBySimplePathWithExplicitRequestParams(@RequestParam("id") final long id) {
return "Get a specific Bar with id=" + id;
}
// with @PathVariable
@RequestMapping(value = "/bars/{numericId:[\\d]+}")
@ResponseBody
public String getBarsBySimplePathWithPathVariable(@PathVariable final long numericId) {
return "Get a specific Bar with id=" + numericId;
}
}
@@ -0,0 +1,55 @@
package com.baeldung.requestmapping;
import java.util.Arrays;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baeldung.web.dto.Bazz;
import com.fasterxml.jackson.core.JsonProcessingException;
@RestController
@RequestMapping("/bazz")
public class BazzNewMappingsExampleController {
@GetMapping
public ResponseEntity<?> getBazzs() throws JsonProcessingException{
List<Bazz> data = Arrays.asList(
new Bazz("1", "Bazz1"),
new Bazz("2", "Bazz2"),
new Bazz("3", "Bazz3"),
new Bazz("4", "Bazz4"));
return new ResponseEntity<>(data, HttpStatus.OK);
}
@GetMapping("/{id}")
public ResponseEntity<?> getBazz(@PathVariable String id){
return new ResponseEntity<>(new Bazz(id, "Bazz"+id), HttpStatus.OK);
}
@PostMapping
public ResponseEntity<?> newBazz(@RequestParam("name") String name){
return new ResponseEntity<>(new Bazz("5", name), HttpStatus.OK);
}
@PutMapping("/{id}")
public ResponseEntity<?> updateBazz(@PathVariable String id,
@RequestParam("name") String name){
return new ResponseEntity<>(new Bazz(id, name), HttpStatus.OK);
}
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteBazz(@PathVariable String id){
return new ResponseEntity<>(new Bazz(id), HttpStatus.OK);
}
}
@@ -0,0 +1,129 @@
package com.baeldung.requestmapping;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(value = "/ex")
public class FooMappingExamplesController {
public FooMappingExamplesController() {
super();
}
// API
// mapping examples
@RequestMapping(value = "/foos")
@ResponseBody
public String getFoosBySimplePath() {
return "Simple Get some Foos";
}
// with @PathVariable
@RequestMapping(value = "/foos/{id}")
@ResponseBody
public String getFoosBySimplePathWithPathVariable(@PathVariable final long id) {
return "Get a specific Foo with id=" + id;
}
@RequestMapping(value = "/foos/{fooid}/bar/{barid}")
@ResponseBody
public String getFoosBySimplePathWithPathVariables(@PathVariable final long fooid, @PathVariable final long barid) {
return "Get a specific Bar with id=" + barid + " from a Foo with id=" + fooid;
}
// other HTTP verbs
@RequestMapping(value = "/foos", method = RequestMethod.POST)
@ResponseBody
public String postFoos() {
return "Post some Foos";
}
// with headers
@RequestMapping(value = "/foos", headers = "key=val")
@ResponseBody
public String getFoosWithHeader() {
return "Get some Foos with Header";
}
@RequestMapping(value = "/foos", headers = { "key1=val1", "key2=val2" })
@ResponseBody
public String getFoosWithHeaders() {
return "Get some Foos with Header";
}
// @RequestMapping(value = "/foos", method = RequestMethod.GET, headers = "Accept=application/json")
// @ResponseBody
// public String getFoosAsJsonFromBrowser() {
// return "Get some Foos with Header Old";
// }
@RequestMapping(value = "/foos", produces = { "application/json", "application/xml" })
@ResponseBody
public String getFoosAsJsonFromREST() {
return "Get some Foos with Header New";
}
// advanced - multiple mappings
@RequestMapping(value = { "/advanced/bars", "/advanced/foos" })
@ResponseBody
public String getFoosOrBarsByPath() {
return "Advanced - Get some Foos or Bars";
}
@RequestMapping(value = "*")
@ResponseBody
public String getFallback() {
return "Fallback for GET Requests";
}
@RequestMapping(value = "*", method = { RequestMethod.GET, RequestMethod.POST })
@ResponseBody
public String allFallback() {
return "Fallback for All Requests";
}
@RequestMapping(value = "/foos/multiple", method = { RequestMethod.PUT, RequestMethod.POST })
@ResponseBody
public String putAndPostFoos() {
return "Advanced - PUT and POST within single method";
}
// --- Ambiguous Mapping
@GetMapping(value = "foos/duplicate" )
public ResponseEntity<String> duplicate() {
return new ResponseEntity<>("Duplicate", HttpStatus.OK);
}
// uncomment for exception of type java.lang.IllegalStateException: Ambiguous mapping
// @GetMapping(value = "foos/duplicate" )
// public String duplicateEx() {
// return "Duplicate";
// }
@GetMapping(value = "foos/duplicate", produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<String> duplicateXml() {
return new ResponseEntity<>("<message>Duplicate</message>", HttpStatus.OK);
}
@GetMapping(value = "foos/duplicate", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> duplicateJson() {
return new ResponseEntity<>("{\"message\":\"Duplicate\"}", HttpStatus.OK);
}
}
@@ -0,0 +1,6 @@
package com.baeldung.service;
public interface CustomerIdGenerator {
int generateNextId();
}
@@ -0,0 +1,14 @@
package com.baeldung.service;
import com.baeldung.model.Customer;
import java.util.Optional;
public interface CustomerService {
Customer createCustomer(Customer customer);
Optional<Customer> findCustomer(String id);
void updateCustomer(Customer customer);
}
@@ -0,0 +1,17 @@
package com.baeldung.service.impl;
import com.baeldung.service.CustomerIdGenerator;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class CustomerIdGeneratorImpl implements CustomerIdGenerator {
private static final AtomicInteger SEQUENCE = new AtomicInteger();
@Override
public int generateNextId() {
return SEQUENCE.incrementAndGet();
}
}
@@ -0,0 +1,42 @@
package com.baeldung.service.impl;
import com.baeldung.model.Customer;
import com.baeldung.service.CustomerIdGenerator;
import com.baeldung.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
public class CustomerServiceImpl implements CustomerService {
private CustomerIdGenerator customerIdGenerator;
private List<Customer> customers = new ArrayList<>();
@Autowired
public CustomerServiceImpl(CustomerIdGenerator customerIdGenerator) {
this.customerIdGenerator = customerIdGenerator;
}
@Override
public Customer createCustomer(Customer customer) {
customer.setId(Integer.toString(customerIdGenerator.generateNextId()));
customers.add(customer);
return customer;
}
@Override
public Optional<Customer> findCustomer(String id) {
return customers.stream()
.filter(customer -> customer.getId().equals(id))
.findFirst();
}
@Override
public void updateCustomer(Customer customer) {
customers.set(customers.indexOf(customer), customer);
}
}
@@ -0,0 +1,69 @@
package com.baeldung.web.controller.customer;
import com.baeldung.model.Customer;
import com.baeldung.service.CustomerService;
import com.baeldung.web.exception.CustomerNotFoundException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jsonpatch.JsonPatch;
import com.github.fge.jsonpatch.JsonPatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
import javax.validation.Valid;
@RestController
@RequestMapping(value = "/customers")
public class CustomerRestController {
private CustomerService customerService;
private ObjectMapper objectMapper = new ObjectMapper();
@Autowired
public CustomerRestController(CustomerService customerService) {
this.customerService = customerService;
}
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Customer> createCustomer(@RequestBody Customer customer) {
Customer customerCreated = customerService.createCustomer(customer);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(customerCreated.getId())
.toUri();
return ResponseEntity.created(location).build();
}
@PatchMapping(path = "/{id}", consumes = "application/json-patch+json")
public ResponseEntity<Customer> updateCustomer(@PathVariable String id,
@RequestBody JsonPatch patch) {
try {
Customer customer = customerService.findCustomer(id).orElseThrow(CustomerNotFoundException::new);
Customer customerPatched = applyPatchToCustomer(patch, customer);
customerService.updateCustomer(customerPatched);
return ResponseEntity.ok(customerPatched);
} catch (CustomerNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (JsonPatchException | JsonProcessingException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
private Customer applyPatchToCustomer(JsonPatch patch, Customer targetCustomer) throws JsonPatchException, JsonProcessingException {
JsonNode patched = patch.apply(objectMapper.convertValue(targetCustomer, JsonNode.class));
return objectMapper.treeToValue(patched, Customer.class);
}
}
@@ -0,0 +1,24 @@
package com.baeldung.web.controller.status;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class ExampleController {
@RequestMapping(value = "/controller", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity sendViaResponseEntity() {
return new ResponseEntity(HttpStatus.NOT_ACCEPTABLE);
}
@RequestMapping(value = "/exception", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity sendViaException() {
throw new ForbiddenException();
}
}
@@ -0,0 +1,10 @@
package com.baeldung.web.controller.status;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "To show an example of a custom message")
public class ForbiddenException extends RuntimeException {
private static final long serialVersionUID = 6826605655586311552L;
}
@@ -0,0 +1,22 @@
package com.baeldung.web.dto;
public class Bazz {
public String id;
public String name;
public Bazz(String id){
this.id = id;
}
public Bazz(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Bazz [id=" + id + ", name=" + name + "]";
}
}
@@ -0,0 +1,45 @@
package com.baeldung.web.dto;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("Foo")
public class Foo {
private long id;
private String name;
public Foo() {
super();
}
public Foo(final String name) {
super();
this.name = name;
}
public Foo(final long id, final String name) {
super();
this.id = id;
this.name = name;
}
// API
public long getId() {
return id;
}
public void setId(final long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
@@ -0,0 +1,5 @@
package com.baeldung.web.exception;
public class CustomerNotFoundException extends RuntimeException {
}
@@ -0,0 +1,36 @@
openapi: 3.0.1
info:
title: API Title
description: This is a sample API.
version: 1.0.0
servers:
- url: https://host/
paths:
/users:
get:
summary: Get Users
operationId: getUsers
responses:
200:
description: Valid input
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: integer
format: int64
createdAt:
type: string
format: date
description: Creation date
example: "2021-01-30"
username:
type: string
@@ -0,0 +1,36 @@
openapi: 3.0.1
info:
title: API Title
description: This is a sample API.
version: 1.0.0
servers:
- url: https://host/
paths:
/users:
get:
summary: Get Users
operationId: getUsers
responses:
200:
description: Valid input
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: integer
format: int64
customDate:
type: string
pattern: '^\d{4}(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])$'
description: Custom date
example: "20210130"
username:
type: string
@@ -0,0 +1,36 @@
openapi: 3.0.1
info:
title: API Title
description: This is a sample API.
version: 1.0.0
servers:
- url: https://host/
paths:
/users:
get:
summary: Get Users
operationId: getUsers
responses:
200:
description: Valid input
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: integer
format: int64
createdAt:
type: string
format: date-time
description: Creation date and time
example: "2021-01-30T08:30:00Z"
username:
type: string
@@ -0,0 +1,75 @@
swagger: "2.0"
info:
description: "This is a sample server."
version: "1.0.0"
title: "Sample API to send JSON objects as query parameters using OpenAPI 2"
tags:
- name: "tickets"
description: "Send Tickets as JSON Objects"
schemes:
- "https"
- "http"
paths:
/tickets:
get:
tags:
- "tickets"
summary: "Send an JSON Object as a query param"
parameters:
- name: "params"
in: "path"
description: "{\"type\":\"foo\",\"color\":\"green\"}"
required: true
type: "string"
responses:
"200":
description: "Successful operation"
"401":
description: "Unauthorized"
"403":
description: "Forbidden"
"404":
description: "Not found"
post:
tags:
- "tickets"
summary: "Send an JSON Object in body"
parameters:
- name: "params"
in: "body"
description: "Parameter is an JSON object with the `type` and `color` properties that should be serialized as JSON {\"type\":\"foo\",\"color\":\"green\"}"
required: true
schema:
type: string
responses:
"200":
description: "Successful operation"
"401":
description: "Unauthorized"
"403":
description: "Forbidden"
"404":
description: "Not found"
"405":
description: "Invalid input"
/tickets2:
get:
tags:
- "tickets"
summary: "Send an JSON Object in body of get reqest"
parameters:
- name: "params"
in: "body"
description: "Parameter is an JSON object with the `type` and `color` properties that should be serialized as JSON {\"type\":\"foo\",\"color\":\"green\"}"
required: true
schema:
type: string
responses:
"200":
description: "Successful operation"
"401":
description: "Unauthorized"
"403":
description: "Forbidden"
"404":
description: "Not found"
@@ -0,0 +1,37 @@
openapi: 3.0.1
info:
title: Sample API to send JSON objects as query parameters using OpenAPI 3
description: This is a sample server.
version: 1.0.0
servers:
- url: /api
tags:
- name: tickets
description: Send Tickets as JSON Objects
paths:
/tickets:
get:
tags:
- tickets
summary: Send an JSON Object as a query param
parameters:
- name: params
in: query
description: '{"type":"foo","color":"green"}'
required: true
schema:
type: object
properties:
type:
type: "string"
color:
type: "string"
responses:
200:
description: Successful operation
401:
description: Unauthorized
403:
description: Forbidden
404:
description: Not found
@@ -0,0 +1,82 @@
package com.baeldung.requestmapping;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.baeldung.config.MvcConfig;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MvcConfig.class)
@WebAppConfiguration
@AutoConfigureWebClient
public class BazzNewMappingsExampleIntegrationTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void whenGettingAllBazz_thenSuccess() throws Exception{
mockMvc.perform(get("/bazz"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(4)))
.andExpect(jsonPath("$[1].id", is("2")))
.andExpect(jsonPath("$[1].name", is("Bazz2")));
}
@Test
public void whenGettingABazz_thenSuccess() throws Exception{
mockMvc.perform(get("/bazz/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is("1")))
.andExpect(jsonPath("$.name", is("Bazz1")));
}
@Test
public void whenAddingABazz_thenSuccess() throws Exception{
mockMvc.perform(post("/bazz").param("name", "Bazz5"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is("5")))
.andExpect(jsonPath("$.name", is("Bazz5")));
}
@Test
public void whenUpdatingABazz_thenSuccess() throws Exception{
mockMvc.perform(put("/bazz/5").param("name", "Bazz6"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is("5")))
.andExpect(jsonPath("$.name", is("Bazz6")));
}
@Test
public void whenDeletingABazz_thenSuccess() throws Exception{
mockMvc.perform(delete("/bazz/5"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is("5")));
}
}
@@ -0,0 +1,35 @@
package com.baeldung.requestmapping;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@WebMvcTest(FooMappingExamplesController.class)
public class FooMappingExamplesControllerUnitTest {
@Autowired
private MockMvc mvc;
@Test
public void givenAcceptsJson_whenGetDuplicate_thenJsonResponseReturned() throws Exception {
mvc.perform(get("/ex/foos/duplicate")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string("{\"message\":\"Duplicate\"}"));
}
@Test
public void givenAcceptsXml_whenGetDuplicate_thenXmlResponseReturned() throws Exception {
mvc.perform(get("/ex/foos/duplicate")
.accept(MediaType.APPLICATION_XML))
.andExpect(status().isOk())
.andExpect(content().string("<message>Duplicate</message>"));
}
}
@@ -0,0 +1,103 @@
package com.baeldung.responseheaders;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ResponseHeaderLiveTest {
private static final String BASE_URL = "http://localhost:8082/spring-rest";
private static final String SINGLE_BASE_URL = BASE_URL + "/single-response-header";
private static final String FILTER_BASE_URL = BASE_URL + "/filter-response-header";
private static final String SERVICE_SINGLE_RESPONSE_HEADER = "Baeldung-Example-Header";
private static final String SERVICE_FILTER_RESPONSE_HEADER = "Baeldung-Example-Filter-Header";
@Autowired
private TestRestTemplate template;
@Test
public void whenHttpServletResponseRequest_thenObtainResponseWithCorrectHeader() {
final String requestUrl = "/http-servlet-response";
ResponseEntity<String> response = template.getForEntity(SINGLE_BASE_URL + requestUrl, String.class);
HttpHeaders responseHeaders = response.getHeaders();
assertThat(responseHeaders).isNotEmpty();
assertThat(responseHeaders).containsEntry(SERVICE_SINGLE_RESPONSE_HEADER, Arrays.asList("Value-HttpServletResponse"));
}
@Test
public void whenResponseEntityConstructorRequest_thenObtainResponseWithCorrectHeader() {
final String requestUrl = "/response-entity-constructor";
ResponseEntity<String> response = template.getForEntity(SINGLE_BASE_URL + requestUrl, String.class);
HttpHeaders responseHeaders = response.getHeaders();
assertThat(responseHeaders).isNotEmpty();
assertThat(responseHeaders).containsEntry(SERVICE_SINGLE_RESPONSE_HEADER, Arrays.asList("Value-ResponseEntityContructor"));
}
@Test
public void whenResponseEntityConstructorAndMultipleHeadersRequest_thenObtainResponseWithCorrectHeaders() {
final String requestUrl = "/response-entity-contructor-multiple-headers";
ResponseEntity<String> response = template.getForEntity(SINGLE_BASE_URL + requestUrl, String.class);
HttpHeaders responseHeaders = response.getHeaders();
assertThat(responseHeaders).isNotEmpty();
assertThat(responseHeaders).containsEntry(SERVICE_SINGLE_RESPONSE_HEADER, Arrays.asList("Value-ResponseEntityConstructorAndHeaders"));
assertThat(responseHeaders).containsEntry("Accept", Arrays.asList(MediaType.APPLICATION_JSON.toString()));
}
@Test
public void whenResponseEntityBuilderRequest_thenObtainResponseWithCorrectHeader() {
final String requestUrl = "/response-entity-builder";
ResponseEntity<String> response = template.getForEntity(SINGLE_BASE_URL + requestUrl, String.class);
HttpHeaders responseHeaders = response.getHeaders();
assertThat(responseHeaders).isNotEmpty();
assertThat(responseHeaders).containsEntry(SERVICE_SINGLE_RESPONSE_HEADER, Arrays.asList("Value-ResponseEntityBuilder"));
}
@Test
public void whenResponseEntityBuilderAndHttpHeadersRequest_thenObtainResponseWithCorrectHeader() {
final String requestUrl = "/response-entity-builder-with-http-headers";
ResponseEntity<String> response = template.getForEntity(SINGLE_BASE_URL + requestUrl, String.class);
HttpHeaders responseHeaders = response.getHeaders();
assertThat(responseHeaders).isNotEmpty();
assertThat(responseHeaders).containsEntry(SERVICE_SINGLE_RESPONSE_HEADER, Arrays.asList("Value-ResponseEntityBuilderWithHttpHeaders"));
}
@Test
public void whenFilterWithNoExtraHeaderRequest_thenObtainResponseWithCorrectHeader() {
final String requestUrl = "/no-extra-header";
ResponseEntity<String> response = template.getForEntity(FILTER_BASE_URL + requestUrl, String.class);
HttpHeaders responseHeaders = response.getHeaders();
assertThat(responseHeaders).isNotEmpty();
assertThat(responseHeaders).containsEntry(SERVICE_FILTER_RESPONSE_HEADER, Arrays.asList("Value-Filter"));
}
@Test
public void whenFilterWithExtraHeaderRequest_thenObtainResponseWithCorrectHeaders() {
final String requestUrl = "/extra-header";
ResponseEntity<String> response = template.getForEntity(FILTER_BASE_URL + requestUrl, String.class);
HttpHeaders responseHeaders = response.getHeaders();
assertThat(responseHeaders).isNotEmpty();
assertThat(responseHeaders).containsEntry(SERVICE_FILTER_RESPONSE_HEADER, Arrays.asList("Value-Filter"));
assertThat(responseHeaders).containsEntry(SERVICE_SINGLE_RESPONSE_HEADER, Arrays.asList("Value-ExtraHeader"));
}
}
@@ -0,0 +1,17 @@
package com.baeldung.service.impl;
import com.baeldung.service.CustomerIdGenerator;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class CustomerIdGeneratorImplUnitTest {
@Test
public void givenIdGeneratedPreviously_whenGenerated_thenIdIsIncremented(){
CustomerIdGenerator customerIdGenerator = new CustomerIdGeneratorImpl();
int firstId = customerIdGenerator.generateNextId();
assertThat(customerIdGenerator.generateNextId()).isEqualTo(++firstId);
}
}
@@ -0,0 +1,94 @@
package com.baeldung.service.impl;
import com.baeldung.model.Customer;
import com.baeldung.service.CustomerIdGenerator;
import com.baeldung.service.CustomerService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.HashMap;
import java.util.Map;
import static com.baeldung.model.Customer.fromCustomer;
import static java.util.Arrays.asList;
import static java.util.Optional.empty;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@RunWith(MockitoJUnitRunner.class)
public class CustomerServiceImplUnitTest {
@Mock
private CustomerIdGenerator mockCustomerIdGenerator;
private CustomerService customerService;
@Before
public void setup() {
customerService = new CustomerServiceImpl(mockCustomerIdGenerator);
}
@Test
public void whenCustomerIsCreated_thenNewCustomerDetailsAreCorrect() {
Map<String, Boolean> communicationPreferences = new HashMap<>();
communicationPreferences.put("post", true);
communicationPreferences.put("email", true);
Customer customer = new Customer("001-555-1234", asList("Milk", "Eggs"), communicationPreferences);
given(mockCustomerIdGenerator.generateNextId()).willReturn(1);
Customer newCustomer = customerService.createCustomer(customer);
assertThat(newCustomer.getId()).isEqualTo("1");
assertThat(newCustomer.getTelephone()).isEqualTo("001-555-1234");
assertThat(newCustomer.getFavorites()).containsExactly("Milk", "Eggs");
assertThat(newCustomer.getCommunicationPreferences()).isEqualTo(communicationPreferences);
}
@Test
public void givenNonExistentCustomer_whenCustomerIsLookedUp_thenCustomerCanNotBeFound() {
assertThat(customerService.findCustomer("CUST12345")).isEqualTo(empty());
}
@Test
public void whenCustomerIsCreated_thenCustomerCanBeFound() {
Map<String, Boolean> communicationPreferences = new HashMap<>();
communicationPreferences.put("post", true);
communicationPreferences.put("email", true);
Customer customer = new Customer("001-555-1234", asList("Milk", "Eggs"), communicationPreferences);
given(mockCustomerIdGenerator.generateNextId()).willReturn(7890);
customerService.createCustomer(customer);
Customer lookedUpCustomer = customerService.findCustomer("7890").get();
assertThat(lookedUpCustomer.getId()).isEqualTo("7890");
assertThat(lookedUpCustomer.getTelephone()).isEqualTo("001-555-1234");
assertThat(lookedUpCustomer.getFavorites()).containsExactly("Milk", "Eggs");
assertThat(lookedUpCustomer.getCommunicationPreferences()).isEqualTo(communicationPreferences);
}
@Test
public void whenCustomerUpdated_thenDetailsUpdatedCorrectly() {
given(mockCustomerIdGenerator.generateNextId()).willReturn(7890);
Map<String, Boolean> communicationPreferences = new HashMap<>();
communicationPreferences.put("post", true);
communicationPreferences.put("email", true);
Customer customer = new Customer("001-555-1234", asList("Milk", "Eggs"), communicationPreferences);
Customer newCustomer = customerService.createCustomer(customer);
Customer customerWithUpdates = fromCustomer(newCustomer);
customerWithUpdates.setTelephone("001-555-6789");
customerService.updateCustomer(customerWithUpdates);
Customer lookedUpCustomer = customerService.findCustomer("7890").get();
assertThat(lookedUpCustomer.getId()).isEqualTo("7890");
assertThat(lookedUpCustomer.getTelephone()).isEqualTo("001-555-6789");
assertThat(lookedUpCustomer.getFavorites()).containsExactly("Milk", "Eggs");
assertThat(lookedUpCustomer.getCommunicationPreferences()).isEqualTo(communicationPreferences);
}
}
@@ -0,0 +1,49 @@
package com.baeldung.uribuilder;
import static org.junit.Assert.assertEquals;
import java.util.Collections;
import org.junit.Test;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
public class SpringUriBuilderIntegrationTest {
@Test
public void constructUri() {
UriComponents uriComponents = UriComponentsBuilder.newInstance().scheme("http").host("www.baeldung.com").path("/junit-5").build();
assertEquals("http://www.baeldung.com/junit-5", uriComponents.toUriString());
}
@Test
public void constructUriEncoded() {
UriComponents uriComponents = UriComponentsBuilder.newInstance().scheme("http").host("www.baeldung.com").path("/junit 5").build().encode();
assertEquals("http://www.baeldung.com/junit%205", uriComponents.toUriString());
}
@Test
public void constructUriFromTemplate() {
UriComponents uriComponents = UriComponentsBuilder.newInstance().scheme("http").host("www.baeldung.com").path("/{article-name}").buildAndExpand("junit-5");
assertEquals("http://www.baeldung.com/junit-5", uriComponents.toUriString());
}
@Test
public void constructUriWithQueryParameter() {
UriComponents uriComponents = UriComponentsBuilder.newInstance().scheme("http").host("www.google.com").path("/").query("q={keyword}").buildAndExpand("baeldung");
assertEquals("http://www.google.com/?q=baeldung", uriComponents.toUriString());
}
@Test
public void expandWithRegexVar() {
String template = "/myurl/{name:[a-z]{1,5}}/show";
UriComponents uriComponents = UriComponentsBuilder.fromUriString(template).build();
uriComponents = uriComponents.expand(Collections.singletonMap("name", "test"));
assertEquals("/myurl/test/show", uriComponents.getPath());
}
}
@@ -0,0 +1,72 @@
package com.baeldung.web.controller.customer;
import com.baeldung.model.Customer;
import com.baeldung.service.CustomerService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CustomerRestControllerIntegrationTest {
@Autowired
private CustomerService customerService;
@Autowired
private TestRestTemplate testRestTemplate;
@Before
public void setup() {
testRestTemplate.getRestTemplate().setRequestFactory(new HttpComponentsClientHttpRequestFactory());
}
@Test
public void givenExistingCustomer_whenPatched_thenOnlyPatchedFieldsUpdated() {
Map<String, Boolean> communicationPreferences = new HashMap<>();
communicationPreferences.put("post", true);
communicationPreferences.put("email", true);
Customer newCustomer = new Customer("001-555-1234", Arrays.asList("Milk", "Eggs"),
communicationPreferences);
Customer customer = customerService.createCustomer(newCustomer);
String patchBody = "[ { \"op\": \"replace\", \"path\": \"/telephone\", \"value\": \"001-555-5678\" },\n"
+ "{\"op\": \"add\", \"path\": \"/favorites/0\", \"value\": \"Bread\" }]";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("application/json-patch+json"));
ResponseEntity<Customer> patchResponse
= testRestTemplate.exchange("/customers/{id}",
HttpMethod.PATCH,
new HttpEntity<>(patchBody, headers),
Customer.class,
customer.getId());
Customer customerPatched = patchResponse.getBody();
assertThat(patchResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(customerPatched.getId()).isEqualTo(customer.getId());
assertThat(customerPatched.getTelephone()).isEqualTo("001-555-5678");
assertThat(customerPatched.getCommunicationPreferences().get("post")).isTrue();
assertThat(customerPatched.getCommunicationPreferences().get("email")).isTrue();
assertThat(customerPatched.getFavorites()).containsExactly("Bread", "Milk", "Eggs");
}
}
@@ -0,0 +1,101 @@
package com.baeldung.web.controller.customer;
import com.baeldung.model.Customer;
import com.baeldung.service.CustomerService;
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.boot.test.mock.mockito.MockBean;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import java.util.HashMap;
import java.util.Map;
import static java.util.Arrays.asList;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.hamcrest.CoreMatchers.is;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
import static org.springframework.http.MediaType.APPLICATION_JSON;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class CustomerRestControllerUnitTest {
private static final String APPLICATION_JSON_PATCH_JSON = "application/json-patch+json";
@Autowired
private MockMvc mvc;
@MockBean
private CustomerService mockCustomerService;
@Autowired
ApplicationContext context;
@Test
public void whenCustomerCreated_then201ReturnedWithNewCustomerLocation() throws Exception {
Map<String, Boolean> communicationPreferences = new HashMap<>();
communicationPreferences.put("post", true);
communicationPreferences.put("email", true);
Customer customer = new Customer("001-555-1234", asList("Milk", "Eggs"), communicationPreferences);
Customer persistedCustomer = Customer.fromCustomer(customer);
persistedCustomer.setId("1");
given(mockCustomerService.createCustomer(customer)).willReturn(persistedCustomer);
String createCustomerRequestBody = "{"
+ "\"telephone\": \"001-555-1234\",\n"
+ "\"favorites\": [\"Milk\", \"Eggs\"],\n"
+ "\"communicationPreferences\": {\"post\":true, \"email\":true}\n"
+ "}";
mvc.perform(post("/customers")
.contentType(APPLICATION_JSON)
.content(createCustomerRequestBody))
.andExpect(status().isCreated())
.andExpect(redirectedUrlPattern("http://*/customers/1"));
}
@Test
public void givenNonExistentCustomer_whenPatched_then404Returned() throws Exception {
given(mockCustomerService.findCustomer("1")).willReturn(empty());
String patchInstructions = "[{\"op\":\"replace\",\"path\": \"/telephone\",\"value\":\"001-555-5678\"}]";
mvc.perform(patch("/customers/1")
.contentType(APPLICATION_JSON_PATCH_JSON)
.content(patchInstructions))
.andExpect(status().isNotFound());
}
@Test
public void givenExistingCustomer_whenPatched_thenReturnPatchedCustomer() throws Exception {
Map<String, Boolean> communicationPreferences = new HashMap<>();
communicationPreferences.put("post", true);
communicationPreferences.put("email", true);
Customer customer = new Customer("1", "001-555-1234", asList("Milk", "Eggs"), communicationPreferences);
given(mockCustomerService.findCustomer("1")).willReturn(of(customer));
String patchInstructions = "[{\"op\":\"replace\",\"path\": \"/telephone\",\"value\":\"001-555-5678\"}]";
mvc.perform(patch("/customers/1")
.contentType(APPLICATION_JSON_PATCH_JSON)
.content(patchInstructions))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", is("1")))
.andExpect(jsonPath("$.telephone", is("001-555-5678")))
.andExpect(jsonPath("$.favorites", is(asList("Milk", "Eggs"))))
.andExpect(jsonPath("$.communicationPreferences", is(communicationPreferences)));
}
}
@@ -0,0 +1,44 @@
package com.baeldung.web.controller.status;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.baeldung.config.MvcConfig;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MvcConfig.class)
@WebAppConfiguration
@AutoConfigureWebClient
public class ExampleControllerIntegrationTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void whenGetRequestSentToController_thenReturnsStatusNotAcceptable() throws Exception {
mockMvc.perform(get("/controller")).andExpect(status().isNotAcceptable());
}
@Test
public void whenGetRequestSentToException_thenReturnsStatusForbidden() throws Exception {
mockMvc.perform(get("/exception")).andExpect(status().isForbidden());
}
}
@@ -0,0 +1,12 @@
## Spring Thymeleaf 3
This module contains articles about Spring with Thymeleaf
## Relevant Articles:
- [Add CSS and JS to Thymeleaf](https://www.baeldung.com/spring-thymeleaf-css-js)
- [Formatting Currencies in Spring Using Thymeleaf](https://www.baeldung.com/spring-thymeleaf-currencies)
- [Working with Select and Option in Thymeleaf](https://www.baeldung.com/thymeleaf-select-option)
- [Conditional CSS Classes in Thymeleaf](https://www.baeldung.com/spring-mvc-thymeleaf-conditional-css-classes)
- [Using Hidden Inputs with Spring and Thymeleaf](https://www.baeldung.com/spring-thymeleaf-hidden-inputs)
- [Thymeleaf Variables](https://www.baeldung.com/thymeleaf-variables)
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 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">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-thymeleaf-3</artifactId>
<name>spring-thymeleaf-3</name>
<packaging>war</packaging>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-2</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.baeldung.thymeleaf.cssandjs.CssAndJsApplication</mainClass>
<layout>JAR</layout>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>${tomcat7-maven-plugin.version}</version>
<executions>
<execution>
<id>tomcat-run</id>
<goals>
<goal>exec-war-only</goal>
</goals>
<phase>package</phase>
<configuration>
<path>/</path>
<enableNaming>false</enableNaming>
<finalName>webapp.jar</finalName>
<charset>utf-8</charset>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<finalName>spring-thymeleaf-3</finalName>
</build>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<tomcat7-maven-plugin.version>2.2</tomcat7-maven-plugin.version>
</properties>
</project>
@@ -0,0 +1,11 @@
package com.baeldung.thymeleaf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@@ -0,0 +1,28 @@
package main.java.com.baeldung.thymeleaf.articles;
public class Article {
private String name;
private String url;
public Article(String name, String url) {
this.name = name;
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
@@ -0,0 +1,37 @@
package main.java.com.baeldung.thymeleaf.articles;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Arrays;
import java.util.List;
@Controller
@RequestMapping("/api/articles")
public class ArticlesController {
@GetMapping
public String allArticles(Model model) {
model.addAttribute("articles", fetchArticles());
return "articles/articles-list";
}
private List<Article> fetchArticles() {
return Arrays.asList(
new Article(
"Introduction to Using Thymeleaf in Spring",
"https://www.baeldung.com/thymeleaf-in-spring-mvc"
),
new Article(
"Spring Boot CRUD Application with Thymeleaf",
"https://www.baeldung.com/spring-boot-crud-thymeleaf"
),
new Article(
"Spring MVC Data and Thymeleaf",
"https://www.baeldung.com/spring-mvc-thymeleaf-data"
)
);
}
}
@@ -0,0 +1,23 @@
package com.baeldung.thymeleaf.blog;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Random;
@Controller
public class BlogController {
@GetMapping("/blog/new")
public String newBlogPost(Model model) {
// Set a random ID so we can see it in the HTML form
BlogDTO blog = new BlogDTO();
blog.setBlogId(Math.abs(new Random().nextLong() % 1000000));
model.addAttribute("blog", blog);
return "blog/blog-new";
}
}
@@ -0,0 +1,66 @@
package com.baeldung.thymeleaf.blog;
import java.util.Date;
public class BlogDTO {
private long blogId;
private String title;
private String body;
private String author;
private String category;
private Date publishedDate;
public long getBlogId() {
return blogId;
}
public void setBlogId(long blogId) {
this.blogId = blogId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public Date getPublishedDate() {
return publishedDate;
}
public void setPublishedDate(Date publishedDate) {
this.publishedDate = publishedDate;
}
}
@@ -0,0 +1,15 @@
package com.baeldung.thymeleaf.conditionalclasses;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ConditionalClassesController {
@GetMapping("/conditional-classes")
public String getConditionalClassesPage( Model model) {
model.addAttribute("condition", true);
return "conditionalclasses/conditionalclasses";
}
}
@@ -0,0 +1,11 @@
package com.baeldung.thymeleaf.cssandjs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CssAndJsApplication {
public static void main(String[] args) {
SpringApplication.run(CssAndJsApplication.class, args);
}
}
@@ -0,0 +1,15 @@
package com.baeldung.thymeleaf.cssandjs;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class CssAndJsController {
@GetMapping("/styled-page")
public String getStyledPage(Model model) {
model.addAttribute("name", "Baeldung Reader");
return "cssandjs/styledPage";
}
}
@@ -0,0 +1,22 @@
package com.baeldung.thymeleaf.currencies;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class CurrenciesController {
@GetMapping(value = "/currency")
public String exchange(
@RequestParam(value = "amount", required = false) String amount,
@RequestParam(value = "amountList", required = false) List amountList,
Locale locale) {
return "currencies/currencies";
}
}
@@ -0,0 +1,60 @@
package com.baeldung.thymeleaf.option;
import java.io.Serializable;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
*
* Simple student POJO with few fields
*
*/
public class Student implements Serializable {
private static final long serialVersionUID = -8582553475226281591L;
@NotNull(message = "Student ID is required.")
@Min(value = 1000, message = "Student ID must be at least 4 digits.")
private Integer id;
@NotNull(message = "Student name is required.")
private String name;
@NotNull(message = "Student gender is required.")
private Character gender;
private Float percentage;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Character getGender() {
return gender;
}
public void setGender(Character gender) {
this.gender = gender;
}
public Float getPercentage() {
return percentage;
}
public void setPercentage(Float percentage) {
this.percentage = percentage;
}
}
@@ -0,0 +1,24 @@
package com.baeldung.thymeleaf.option;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the student model.
*
*/
@Controller
public class StudentController {
@RequestMapping(value = "/student", method = RequestMethod.GET)
public String addStudent(Model model) {
model.addAttribute("student", new Student());
return "option/studentForm.html";
}
}
@@ -0,0 +1,7 @@
function showAlert() {
alert("The button was clicked!");
}
function showName(name) {
alert("Here's the name: " + name);
}
@@ -0,0 +1,18 @@
h2 {
font-family: sans-serif;
font-size: 1.5em;
text-transform: uppercase;
}
strong {
font-weight: 700;
background-color: yellow;
}
p {
font-family: sans-serif;
}
label {
font-weight: 600;
}
@@ -0,0 +1,39 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Thymeleaf Variables</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<main>
<div th:each="article : ${articles}">
<a th:text="${article.name}" th:href="${article.url}"></a>
</div>
<div th:with="firstArticle=${articles[0]}">
<a th:text="${firstArticle.name}" th:href="${firstArticle.url}"></a>
</div>
<div th:each="article : ${articles}" , th:with="articleName=${article.name}">
<a th:text="${articleName}" th:href="${article.url}"></a>
</div>
<div th:each="article : ${articles}" th:with="articleName=${article.name}, articleUrl=${article.url}">
<a th:text="${articleName}" th:href="${articleUrl}"></a>
</div>
<div id="firstDiv" th:with="firstArticle=${articles[0]}">
<a th:text="${firstArticle.name}" th:href="${firstArticle.url}"></a>
</div>
<div id="secondDiv">
<h2 th:text="${firstArticle.name}"></h2>
</div>
<div id="mainDiv" th:with="articles = ${ { articles[0], articles[1] } }">
<div th:each="article : ${articles}">
<a th:text="${article.name}" th:href="${article.url}"></a>
</div>
</div>
</main>
</body>
</html>
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hidden Input Examples</title>
</head>
<body>
<h2>Hidden Input Example 1</h2>
<form action="#" method="post" th:action="@{/blog}" th:object="${blog}">
<input type="text" th:field="*{title}">
<input type="text" th:field="*{category}">
<textarea th:field="*{body}"></textarea>
<input type="hidden" th:field="*{blogId}" id="blogId">
<input type="submit">
</form>
<h2>Hidden Input Example 2</h2>
<form action="#" method="post" th:action="@{/blog}" th:object="${blog}">
<input type="text" th:field="*{title}">
<input type="text" th:field="*{category}">
<textarea th:field="*{body}"></textarea>
<input type="hidden" th:value="${blog.blogId}" th:attr="name='blogId'"/>
<input type="submit">
</form>
<h2>Hidden Input Example 3</h2>
<form action="#" method="post" th:action="@{/blog}" th:object="${blog}">
<input type="text" th:field="*{title}">
<input type="text" th:field="*{category}">
<textarea th:field="*{body}"></textarea>
<input type="hidden" th:value="${blog.blogId}" name="blogId"/>
<input type="submit">
</form>
</body>
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Conditional CSS Classes in Thymeleaf</title>
</head>
<body>
<h2>The Goal</h2>
<p>
<span class="base condition-true">
I have two classes: "base" and either "condition-true" or "condition-false" depending on a server-side condition.
</span>
</p>
<h2>Using th:if</h2>
<p>
<span th:if="${condition}" class="base condition-true">
This HTML is duplicated. We probably want a better solution.
</span>
<span th:if="${!condition}" class="base condition-false">
This HTML is duplicated. We probably want a better solution.
</span>
</p>
<h2>Using th:attr</h2>
<p>
<span th:attr="class=${condition ? 'base condition-true' : 'base condition-false'}">
This HTML is consolidated, which is good, but the Thymeleaf attribute still has some redundancy in it.
</span>
</p>
<h2>Using th:class</h2>
<p>
<span th:class="'base '+${condition ? 'condition-true' : 'condition-false'}">
The base CSS class still has to be appended with String concatenation. We can do a little bit better.
</span>
</p>
<h2>Using th:classappend</h2>
<p>
<span class="base" th:classappend="${condition ? 'condition-true' : 'condition-false'}">
This HTML is consolidated, and the conditional class is appended separately from the static base class.
</span>
</p>
</body>
</html>
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Add CSS and JS to Thymeleaf</title>
<link th:href="@{/styles/cssandjs/main.css}" rel="stylesheet" />
<script th:inline="javascript">
var nameJs = /*[[${name}]]*/;
</script>
<script type="text/javascript" th:src="@{/js/cssandjs/actions.js}"></script>
</head>
<body>
<h2>Carefully Styled Heading</h2>
<p>
This is text on which we want to apply <strong>very special</strong> styling.
</p>
<p><label>Name:</label><span th:text="${name}"></span></p>
<button type="button" th:onclick="showName(nameJs);">Show Name</button>
</body>
</html>
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Currency table</title>
</head>
<body>
<p>Currency format by Locale</p>
<p th:text="${#numbers.formatCurrency(param.amount)}"></p>
<p>Currency Arrays format by Locale</p>
<p th:text="${#numbers.listFormatCurrency(param.amountList)}"></p>
<p>Remove decimal values</p>
<p th:text="${#strings.replace(#numbers.formatCurrency(param.amount), '.00', '')}"></p>
<p>Replace decimal points</p>
<p th:text="${#numbers.formatDecimal(param.amount, 1, 2, 'COMMA')}"></p>
</body>
</html>
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Student Form</title>
</head>
<body>
<h1>Student Form</h1>
<form action="#" th:action="@{/saveStudent}" th:object="${student}"
method="post">
<ul>
<li th:errors="*{id}" />
<li th:errors="*{name}" />
<li th:errors="*{gender}" />
<li th:errors="*{percentage}" />
</ul>
<table border="1">
<tr>
<td><label th:text="ID" /></td>
<td><input type="number" th:field="*{id}" /></td>
</tr>
<tr>
<td><label th:text="Name" /></td>
<td><input type="text" th:field="*{name}" /></td>
</tr>
<tr>
<td><label th:text="Gender" /></td>
<td><select th:field="*{gender}">
<option th:value="'M'" th:text="Male"></option>
<option th:value="'F'" th:text="Female"></option>
</select></td>
</tr>
<tr>
<td><label th:text="Percentage" /></td>
<td><select id="percentage" name="percentage">
<option th:each="i : ${#numbers.sequence(0, 100)}" th:value="${i}" th:text="${i}" th:selected="${i==75}"></option>
</select></td>
</tr>
</table>
</form>
</body>
</html>
@@ -0,0 +1,13 @@
package com.baeldung.thymeleaf;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ApplicationIntegrationTest {
@Test
public void contextLoads() {
}
}
@@ -0,0 +1,41 @@
package com.baeldung.thymeleaf.cssandjs;
import static org.hamcrest.CoreMatchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = CssAndJsApplication.class)
public class CssAndJsControllerIntegrationTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
@Test
public void whenCalledGetStyledPage_thenReturnContent() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.get("/styled-page"))
.andExpect(status().isOk())
.andExpect(view().name("cssandjs/styledPage"))
.andExpect(content().string(containsString("Carefully Styled Heading")));
}
}
@@ -0,0 +1,68 @@
package com.baeldung.thymeleaf.currencies;
import static org.hamcrest.CoreMatchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
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.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc(printOnlyOnFailure = false)
public class CurrenciesControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenCallCurrencyWithSpanishLocale_ThenReturnProperCurrency() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
.header("Accept-Language", "es-ES")
.param("amount", "10032.5"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("10.032,50")));
}
@Test
public void whenCallCurrencyWithUSALocale_ThenReturnProperCurrency() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
.header("Accept-Language", "en-US")
.param("amount", "10032.5"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("$10,032.50")));
}
@Test
public void whenCallCurrencyWithRomanianLocaleWithArrays_ThenReturnLocaleCurrencies() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
.header("Accept-Language", "en-GB")
.param("amountList", "10", "20", "30"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("£10.00, £20.00, £30.00")));
}
@Test
public void whenCallCurrencyWithUSALocaleWithoutDecimal_ThenReturnCurrencyWithoutTrailingZeros() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
.header("Accept-Language", "en-US")
.param("amount", "10032"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("$10,032")));
}
@Test
public void whenCallCurrencyWithUSALocale_ThenReturnReplacedDecimalPoint() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/currency")
.header("Accept-Language", "en-US")
.param("amount", "1.5"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("1,5")));
}
}