diff --git a/README.adoc b/README.adoc
index 87f013d..dea9c4d 100644
--- a/README.adoc
+++ b/README.adoc
@@ -12,6 +12,7 @@ We have separate folders for each of these:
== Spring HATEOAS Modules
* link:basics[Basics] - Poke and prod at a hypermedia-powered service from inside the code as well as externally using standard tools
+* link:simplified[Simplified] - Use Spring HATEOAS in the simplest way possible.
* link:api-evolution[API Evolution] - Upgrade an existing REST resource
* link:hypermedia[Hypermedia] - Create hypermedia-driven REST resources, linking them together, and supporting older links.
* link:affordances[Affordances] - Create richer hypermedia controls using more complex hypermedia formats
diff --git a/pom.xml b/pom.xml
index 79b6192..eb7c5a5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,7 +39,7 @@
org.springframework.boot
spring-boot-starter-parent
- 2.0.0.M7
+ 2.0.0.RC1
@@ -49,6 +49,7 @@
api-evolution
hypermedia
affordances
+ simplified
diff --git a/simplified/README.adoc b/simplified/README.adoc
new file mode 100644
index 0000000..02dbabe
--- /dev/null
+++ b/simplified/README.adoc
@@ -0,0 +1,223 @@
+= Spring HATEOAS - Basic Example
+
+This guides shows how to add Spring HATEOAS in the simplest way possible. Like the rest of these examples, it uses a payroll system.
+
+NOTE: This example uses https://projectlombok.org[Project Lombok] to reduce writing Java code.
+
+== Defining Your Domain
+
+The cornerstone of any example is the domain object:
+
+[source,java]
+----
+@Data
+@Entity
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor
+class Employee {
+
+ @Id @GeneratedValue
+ private Long id;
+ private String firstName;
+ private String lastName;
+ private String role;
+
+ ...
+}
+----
+
+This domain object includes:
+
+* `@Data` - Lombok annotation to define a mutable value object
+* `@Entity` - JPA annotation to make the object storagable in a classic SQL engine (H2 in this example)
+* `@NoArgsConstructor(PRIVATE)` - Lombok annotation to create an empty constructor call to appease Jackson, but which is private and not usable to our app's code.
+* `@AllArgsConstructor` - Lombok annotation to create an all-arg constructor for certain test scenarios
+
+== Accessing Data
+
+To experiment with something realistic, you need to access a real database. This example leverages H2, an embedded JPA datasource.
+And while it's not a requirement for Spring HATEOAS, this example uses Spring Data JPA.
+
+Create a repository like this:
+
+[source,java]
+----
+interface EmployeeRepository extends CrudRepository {
+}
+----
+
+This interface extends Spring Data Commons' `CrudRepository`, inheriting a collection of create/replace/update/delete (CRUD)
+operations.
+
+[[converting-entities-to-resources]]
+== Converting Entities to Resources
+
+In REST, the "thing" being linked to is a *resource*. Resources provide both information as well as details on _how_ to
+retrieve and update that information.
+
+Spring HATEOAS defines a generic `Resource` container that lets you store any domain object (`Employee` in this example), and
+add additional links.
+
+IMPORTANT: Spring HATEOAS's `Resource` and `Link` classes are *vendor neutral*. HAL is thrown around a lot, being the
+default mediatype, but these classes can be used to render any mediatype.
+
+The following Spring MVC controller defines the application's routes, and hence is the source of links needed
+in the hypermedia.
+
+NOTE: This guide assumes you already somewhat familiar with Spring MVC.
+
+[source,java]
+----
+@RestController
+class EmployeeController {
+
+ private final EmployeeRepository repository;
+
+ EmployeeController(EmployeeRepository repository) {
+ this.repository = repository;
+ }
+
+ ...
+}
+----
+
+This piece of code shows how the Spring MVC controller is wired with a copy of the `EmployeeRepository` through
+constructor injection and marked as a *REST controller* thanks to the `@RestController` annotation.
+
+The route for the https://martinfowler.com/bliki/DDD_Aggregate.html[aggregate root] is shown below:
+
+[source,java]
+----
+/**
+ * Look up all employees, and transform them into a REST collection resource.
+ * Then return them through Spring Web's {@link ResponseEntity} fluent API.
+ *
+ * NOTE: cURL will fetch things as HAL JSON directly, but browsers issue a different
+ * default accept header, which allows XML to get requested first, so "produces"
+ * forces it to HAL JSON for all clients.
+ */
+@GetMapping(value = "/employees", produces = MediaTypes.HAL_JSON_VALUE)
+ResponseEntity>> findAll() {
+
+ List> employees = StreamSupport.stream(repository.findAll().spliterator(), false)
+ .map(employee -> new Resource<>(employee,
+ linkTo(methodOn(EmployeeController.class).findOne(employee.getId())).withSelfRel(),
+ linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees")))
+ .collect(Collectors.toList());
+
+ return ResponseEntity.ok(
+ new Resources<>(employees,
+ linkTo(methodOn(EmployeeController.class).findAll()).withSelfRel()));
+}
+----
+
+It retrieves a collection of `Employee` objects, streams through a Java 8 spliterator, and converts them into a collection
+of `Resource` objects by using Spring HATEOAS's `linkTo` and `methodOn` helpers to build links.
+
+* The natural convention with REST endpoints is to serve a *self* link (denoted by the `.withSelfRel()` call).
+* It's also useful for any single item resource to include a link back to the aggregate (denoted by the `.withRel("employees")`).
+
+The whole collection of single item resources is then wrapped in a Spring HATEOAS `Resources` type.
+
+NOTE: `Resources` is Spring HATEOAS's vendor neutral representation of a collection. It has it's
+own set of links, separate from the links of each member of the collection. That's why the whole
+structure is `Resources>` and not `Resources`.
+
+To build a single resource, the `/employees/{id}` route is shown below:
+
+[source,java]
+----
+/**
+ * Look up a single {@link Employee} and transform it into a REST resource. Then return it through
+ * Spring Web's {@link ResponseEntity} fluent API.
+ *
+ * See {@link #findAll()} to explain {@link GetMapping}'s "produces" argument.
+ *
+ * @param id
+ */
+@GetMapping(value = "/employees/{id}", produces = MediaTypes.HAL_JSON_VALUE)
+ResponseEntity> findOne(@PathVariable long id) {
+
+ return repository.findById(id)
+ .map(employee -> new Resource<>(employee,
+ linkTo(methodOn(EmployeeController.class).findOne(employee.getId())).withSelfRel(),
+ linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees")))
+ .map(ResponseEntity::ok)
+ .orElse(ResponseEntity.notFound().build());
+}
+----
+
+This code is almost identical. It fetches a single item `Employee` from the database and that wraps up into a
+`Resource` object with the same links, but that's it. No need to create a `Resources` object since is NOT a
+collection.
+
+IMPORTANT: Does this look like duplicate code found in the aggregate root? Sures it does. That's why Spring HATEOAS
+ includes the ability to define a `ResourceAssembler`. It lets you define, in one place, all the links for a given
+ entity type. Then you can reuse it as needed in all relevant controller methods. It's been left out of this section
+ for the sake of simplicity.
+
+== Testing Hypermedia
+
+Nothing is complete without testing. Thanks to Spring Boot, it's easier than ever to test a Spring MVC controller,
+including the generated hypermedia.
+
+The following is a bare bones "slice" test case:
+
+[source,java]
+----
+@RunWith(SpringRunner.class)
+@WebMvcTest(EmployeeController.class)
+public class EmployeeControllerTests {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @MockBean
+ private EmployeeRepository repository;
+
+ ...
+}
+----
+
+* `@RunWith(SpringRunner.class)` is needed to leverage Spring Boot's test annotations with JUnit.
+* `@WebMvcTest(EmployeeController.class)` confines Spring Boot to only autoconfiguring Spring MVC components, and _only_
+this one controller, making it a very precise test case.
+* `@Autowired MockMvc` gives us a handle on a Spring Mock tester.
+* `@MockBean` flags `EmployeeRepository` as a test collaborator, since we don't plan on talking to a real database in this test case.
+
+With this structure, we can start crafting a test case!
+
+[source,java]
+----
+@Test
+public void getShouldFetchAHalDocument() throws Exception {
+
+ given(repository.findAll()).willReturn(
+ Arrays.asList(
+ new Employee(1L,"Frodo", "Baggins", "ring bearer"),
+ new Employee(2L,"Bilbo", "Baggins", "burglar")));
+
+ mvc.perform(get("/employees").accept(MediaTypes.HAL_JSON_VALUE))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$._embedded.employees[0].id", is(1)))
+ ...
+}
+----
+
+* At first, the test case uses Mockito's `given()` method to define the "given"s of the test.
+* Next, it uses Spring Mock MVC's `mvc` to `perform()` a *GET /employees* call with an accept header of HAL's mediatype.
+* As a courtesy, it uses the `.andDo(print())` to give us a complete print out of the whole thing on the console.
+* Finally, it chains a whole series of assertions.
+** Verify HTTP status is *200 OK*.
+** Verify the response *Content-Type* header is also HAL's mediatype (with UTF-8 flavor).
+** Verify that the JSON Path of *$._embedded.employees[0].id* is `1`.
+** And so forth...
+
+The rest of the assertions are commented out, but you can read it in the source code.
+
+NOTE: This is not the only way to assert the results. See Spring Framework reference docs and Spring HATEOAS
+test cases for more examples.
+
+For the next step in Spring HATEOAS, you may wish to read link:../api-evolution[Spring HATEOAS - API Evolution Example].
\ No newline at end of file
diff --git a/simplified/pom.xml b/simplified/pom.xml
new file mode 100644
index 0000000..c19c4e0
--- /dev/null
+++ b/simplified/pom.xml
@@ -0,0 +1,34 @@
+
+
+ 4.0.0
+
+ spring-hateoas-examples-simplified
+ Spring HATEOAS - Examples - Simplified
+ jar
+
+
+ org.springframework.hateoas.examples
+ spring-hateoas-examples
+ 1.0.0.BUILD-SNAPSHOT
+
+
+
+
+ org.springframework.hateoas.examples
+ commons
+ 1.0.0.BUILD-SNAPSHOT
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
\ No newline at end of file
diff --git a/simplified/src/main/java/org/springframework/hateoas/examples/DatabaseLoader.java b/simplified/src/main/java/org/springframework/hateoas/examples/DatabaseLoader.java
new file mode 100644
index 0000000..9583481
--- /dev/null
+++ b/simplified/src/main/java/org/springframework/hateoas/examples/DatabaseLoader.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.hateoas.examples;
+
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+/**
+ * Pre-load some data using a Spring Boot {@link CommandLineRunner}.
+ *
+ * @author Greg Turnquist
+ */
+@Component
+class DatabaseLoader {
+
+ /**
+ * Use Spring to inject a {@link EmployeeRepository} that can then load data. Since this will run
+ * only after the app is operational, the database will be up.
+ *
+ * @param repository
+ */
+ @Bean
+ CommandLineRunner init(EmployeeRepository repository) {
+ return args -> {
+ repository.save(new Employee("Frodo", "Baggins", "ring bearer"));
+ repository.save(new Employee("Bilbo", "Baggins", "burglar"));
+ };
+ }
+
+}
diff --git a/simplified/src/main/java/org/springframework/hateoas/examples/Employee.java b/simplified/src/main/java/org/springframework/hateoas/examples/Employee.java
new file mode 100644
index 0000000..e63ee24
--- /dev/null
+++ b/simplified/src/main/java/org/springframework/hateoas/examples/Employee.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.hateoas.examples;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+/**
+ * Domain object representing a company employee. Project Lombok keeps actual code at a minimum.
+ *
+ * {@code @Data} - Generates getters, setters, toString, hash, and equals functions
+ * {@code @Entity} - JPA annotation to flag this class for DB persistence
+ * {@code @NoArgsConstructor} - Create a constructor with no args to support JPA
+ * {@code @AllArgsConstructor} - Create a constructor with all args to support testing
+ *
+ * {@code @JsonIgnoreProperties(ignoreUnknow=true)}
+ * When converting JSON to Java, ignore any unrecognized attributes. This is critical for REST because it
+ * encourages adding new fields in later versions that won't break. It also allows things like _links to be
+ * ignore as well, meaning HAL documents can be fetched and later posted to the server without adjustment.
+ *
+ *
+ * @author Greg Turnquist
+ */
+@Data
+@Entity
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor
+class Employee {
+
+ @Id @GeneratedValue
+ private Long id;
+ private String firstName;
+ private String lastName;
+ private String role;
+
+ /**
+ * Useful constructor when id is not yet known.
+ *
+ * @param firstName
+ * @param lastName
+ * @param role
+ */
+ Employee(String firstName, String lastName, String role) {
+
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.role = role;
+ }
+}
diff --git a/simplified/src/main/java/org/springframework/hateoas/examples/EmployeeController.java b/simplified/src/main/java/org/springframework/hateoas/examples/EmployeeController.java
new file mode 100644
index 0000000..81ab6a1
--- /dev/null
+++ b/simplified/src/main/java/org/springframework/hateoas/examples/EmployeeController.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.hateoas.examples;
+
+import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.MediaTypes;
+import org.springframework.hateoas.Resource;
+import org.springframework.hateoas.Resources;
+import org.springframework.http.ResponseEntity;
+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.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Spring Web {@link RestController} used to generate a REST API.
+ *
+ * @author Greg Turnquist
+ */
+@RestController
+class EmployeeController {
+
+ private final EmployeeRepository repository;
+
+ EmployeeController(EmployeeRepository repository) {
+ this.repository = repository;
+ }
+
+ /**
+ * Look up all employees, and transform them into a REST collection resource.
+ * Then return them through Spring Web's {@link ResponseEntity} fluent API.
+ *
+ * NOTE: cURL will fetch things as HAL JSON directly, but browsers issue a different
+ * default accept header, which allows XML to get requested first, so "produces"
+ * forces it to HAL JSON for all clients.
+ */
+ @GetMapping(value = "/employees", produces = MediaTypes.HAL_JSON_VALUE)
+ ResponseEntity>> findAll() {
+
+ List> employees = StreamSupport.stream(repository.findAll().spliterator(), false)
+ .map(employee -> new Resource<>(employee,
+ linkTo(methodOn(EmployeeController.class).findOne(employee.getId())).withSelfRel(),
+ linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees")))
+ .collect(Collectors.toList());
+
+ return ResponseEntity.ok(
+ new Resources<>(employees,
+ linkTo(methodOn(EmployeeController.class).findAll()).withSelfRel()));
+ }
+
+ @PostMapping("/employees")
+ ResponseEntity> newEmployee(@RequestBody Employee employee) {
+
+ try {
+ Employee savedEmployee = repository.save(employee);
+
+ Resource employeeResource = new Resource<>(savedEmployee,
+ linkTo(methodOn(EmployeeController.class).findOne(savedEmployee.getId())).withSelfRel());
+
+ return ResponseEntity
+ .created(new URI(employeeResource.getRequiredLink(Link.REL_SELF).getHref()))
+ .body(employeeResource);
+ } catch (URISyntaxException e) {
+ return ResponseEntity.badRequest().body("Unable to create " + employee);
+ }
+ }
+
+ /**
+ * Look up a single {@link Employee} and transform it into a REST resource. Then return it through
+ * Spring Web's {@link ResponseEntity} fluent API.
+ *
+ * See {@link #findAll()} to explain {@link GetMapping}'s "produces" argument.
+ *
+ * @param id
+ */
+ @GetMapping(value = "/employees/{id}", produces = MediaTypes.HAL_JSON_VALUE)
+ ResponseEntity> findOne(@PathVariable long id) {
+
+ return repository.findById(id)
+ .map(employee -> new Resource<>(employee,
+ linkTo(methodOn(EmployeeController.class).findOne(employee.getId())).withSelfRel(),
+ linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees")))
+ .map(ResponseEntity::ok)
+ .orElse(ResponseEntity.notFound().build());
+ }
+
+ /**
+ * Update existing employee then return a Location header.
+ *
+ * @param employee
+ * @param id
+ * @return
+ */
+ @PutMapping("/employees/{id}")
+ ResponseEntity> updateEmployee(@RequestBody Employee employee, @PathVariable long id) {
+
+ Employee employeeToUpdate = employee;
+ employeeToUpdate.setId(id);
+ repository.save(employeeToUpdate);
+
+ Link newlyCreatedLink = linkTo(methodOn(EmployeeController.class).findOne(id)).withSelfRel();
+
+ try {
+ return ResponseEntity.noContent()
+ .location(new URI(newlyCreatedLink.getHref()))
+ .build();
+ } catch (URISyntaxException e) {
+ return ResponseEntity.badRequest().body("Unable to update " + employeeToUpdate);
+ }
+ }
+
+}
diff --git a/simplified/src/main/java/org/springframework/hateoas/examples/EmployeeRepository.java b/simplified/src/main/java/org/springframework/hateoas/examples/EmployeeRepository.java
new file mode 100644
index 0000000..61ec4c8
--- /dev/null
+++ b/simplified/src/main/java/org/springframework/hateoas/examples/EmployeeRepository.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.hateoas.examples;
+
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * A simple Spring Data {@link CrudRepository} for storing {@link Employee}s.
+ *
+ * @author Greg Turnquist
+ */
+interface EmployeeRepository extends CrudRepository {
+}
diff --git a/simplified/src/main/java/org/springframework/hateoas/examples/SpringHateoasSimplifiedApplication.java b/simplified/src/main/java/org/springframework/hateoas/examples/SpringHateoasSimplifiedApplication.java
new file mode 100644
index 0000000..8df9ea4
--- /dev/null
+++ b/simplified/src/main/java/org/springframework/hateoas/examples/SpringHateoasSimplifiedApplication.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.hateoas.examples;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.hateoas.core.EvoInflectorRelProvider;
+
+/**
+ * @author Greg Turnquist
+ */
+@SpringBootApplication
+public class SpringHateoasSimplifiedApplication {
+
+ public static void main(String... args) {
+ SpringApplication.run(SpringHateoasSimplifiedApplication.class);
+ }
+
+ /**
+ * Format embedded collections by pluralizing the resource's type.
+ *
+ * @return
+ */
+ @Bean
+ EvoInflectorRelProvider relProvider() {
+ return new EvoInflectorRelProvider();
+ }
+}
diff --git a/simplified/src/test/java/org/springframework/hateoas/examples/EmployeeControllerTests.java b/simplified/src/test/java/org/springframework/hateoas/examples/EmployeeControllerTests.java
new file mode 100644
index 0000000..7292335
--- /dev/null
+++ b/simplified/src/test/java/org/springframework/hateoas/examples/EmployeeControllerTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.hateoas.examples;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.mockito.BDDMockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+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.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.hateoas.MediaTypes;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+/**
+ * How to test the hypermedia-based {@link EmployeeController} with everything else mocked out.
+ *
+ * @author Greg Turnquist
+ */
+@RunWith(SpringRunner.class)
+@WebMvcTest(EmployeeController.class)
+public class EmployeeControllerTests {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @MockBean
+ private EmployeeRepository repository;
+
+ @Test
+ public void getShouldFetchAHalDocument() throws Exception {
+
+ given(repository.findAll()).willReturn(
+ Arrays.asList(
+ new Employee(1L,"Frodo", "Baggins", "ring bearer"),
+ new Employee(2L,"Bilbo", "Baggins", "burglar")));
+
+ mvc.perform(get("/employees").accept(MediaTypes.HAL_JSON_VALUE))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_UTF8_VALUE))
+ .andExpect(jsonPath("$._embedded.employees[0].id", is(1)))
+ .andExpect(jsonPath("$._embedded.employees[0].firstName", is("Frodo")))
+ .andExpect(jsonPath("$._embedded.employees[0].lastName", is("Baggins")))
+ .andExpect(jsonPath("$._embedded.employees[0].role", is("ring bearer")))
+ .andExpect(jsonPath("$._embedded.employees[0]._links.self.href", is("http://localhost/employees/1")))
+ .andExpect(jsonPath("$._embedded.employees[0]._links.employees.href", is("http://localhost/employees")))
+ .andExpect(jsonPath("$._embedded.employees[1].id", is(2)))
+ .andExpect(jsonPath("$._embedded.employees[1].firstName", is("Bilbo")))
+ .andExpect(jsonPath("$._embedded.employees[1].lastName", is("Baggins")))
+ .andExpect(jsonPath("$._embedded.employees[1].role", is("burglar")))
+ .andExpect(jsonPath("$._embedded.employees[1]._links.self.href", is("http://localhost/employees/2")))
+ .andExpect(jsonPath("$._embedded.employees[1]._links.employees.href", is("http://localhost/employees")))
+ .andExpect(jsonPath("$._links.self.href", is("http://localhost/employees")))
+ .andReturn();
+ }
+}