Spring Boot Hamcrest Tutorial

In this tutorial, we will demonstrate how to integrate Hamcrest into a Spring Boot application and write JUnit tests for CRUD operations.

To learn more about Hamcrest, check out this guide: Hamcrest.

Quick Introduction to Hamcrest

Hamcrest is a popular library for writing matcher objects, which are used for making assertions in tests, particularly in unit tests. It is widely used in Java testing frameworks like JUnit and TestNG. Hamcrest provides a rich set of matchers that allow you to express complex conditions in a readable and declarative manner.

Key Features of Hamcrest:

  1. Readable Assertions:

    • Hamcrest's matchers make test assertions more readable and expressive. Instead of using simple boolean checks, you can write conditions that are easier to understand at a glance.
  2. Flexible Matchers:

    • Hamcrest includes a wide variety of matchers for different types of assertions, such as checking for equality, collections, strings, exceptions, and more.
  3. Combining Matchers:

    • You can combine matchers to form complex assertions. For example, you can check that a collection has a specific size and contains specific elements.
  4. Custom Matchers:

    • Hamcrest allows you to create custom matchers if the built-in ones do not meet your requirements, providing flexibility to tailor the assertions to your specific needs.

Example Usage:

Here’s a simple example of using Hamcrest with JUnit:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

import java.util.Arrays;
import java.util.List;

import org.junit.Test;

public class HamcrestExampleTest {

    @Test
    public void testListContainsElements() {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Orange");

        // Using Hamcrest matchers
        assertThat(fruits, hasSize(3));
        assertThat(fruits, contains("Apple", "Banana", "Orange"));
        assertThat(fruits, hasItem("Banana"));
        assertThat("Banana", both(startsWith("Ban")).and(containsString("ana")));
    }
}

Commonly Used Matchers:

  • Core Matchers:

    • is(T value): Checks if the object is equal to the given value.
    • not(Matcher<T> matcher): Checks if the object does not match the given matcher.
    • anyOf(Matcher<T>... matchers): Checks if the object matches any of the given matchers.
    • allOf(Matcher<T>... matchers): Checks if the object matches all of the given matchers.
  • Collection Matchers:

    • hasSize(int size): Checks if the collection has the specified size.
    • contains(T... items): Checks if the collection contains the specified items in the given order.
    • hasItem(T item): Checks if the collection contains the specified item.
  • String Matchers:

    • containsString(String substring): Checks if the string contains the given substring.
    • startsWith(String prefix): Checks if the string starts with the given prefix.
    • endsWith(String suffix): Checks if the string ends with the given suffix.

Benefits of Using Hamcrest:

  • Improved Readability: The expressive syntax makes test assertions easier to read and understand.
  • Rich Functionality: A wide range of built-in matchers covers most common needs.
  • Extensibility: Custom matchers can be created to handle specific scenarios.

Hamcrest's integration with testing frameworks like JUnit makes it a powerful tool for writing clear and maintainable test assertions.

Prerequisites

  1. Java Development Kit (JDK) 17 or later
  2. Apache Maven installed
  3. An IDE like IntelliJ IDEA or Eclipse

Step 1: Create a Spring Boot Project

You can create a Spring Boot project using Spring Initializr or your IDE.

Using Spring Initializr

  1. Go to Spring Initializr.
  2. Select the following options:
    • Project: Maven Project
    • Language: Java
    • Spring Boot: 3.0.0 or later
    • Group: com.example
    • Artifact: hamcrest-demo
    • Name: hamcrest-demo
    • Package name: com.example.hamcrestdemo
    • Packaging: Jar
    • Java: 17 or later
  3. Add the following dependencies:
    • Spring Web
    • Spring Data JPA
    • H2 Database
    • Spring Boot Starter Test
  4. Click "Generate" to download the project zip file.
  5. Extract the zip file and open the project in your IDE.

Step 2: Add Hamcrest Dependency

Add the following dependencies to your pom.xml file:

<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Boot Starter Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- H2 Database -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Spring Boot Starter Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- Hamcrest -->
    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest</artifactId>
        <version>2.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Step 3: Configure Application Properties

Add the following properties to src/main/resources/application.properties:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Step 4: Create Entity and Repository Classes

Create the Employee Entity

Create a new Java class named Employee in the com.example.hamcrestdemo package:

package com.example.hamcrestdemo;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

    // Constructors
    public Employee() {}

    public Employee(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Create the Employee Repository

Create a new Java interface named EmployeeRepository in the com.example.hamcrestdemo package:

package com.example.hamcrestdemo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

Step 5: Create the Service Class

EmployeeService

Create a new Java class named EmployeeService in the com.example.hamcrestdemo package:

package com.example.hamcrestdemo;

import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public EmployeeService(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    public List<Employee> findAll() {
        return employeeRepository.findAll();
    }

    public Optional<Employee> findById(Long id) {
        return employeeRepository.findById(id);
    }

    public Employee save(Employee employee) {
        return employeeRepository.save(employee);
    }

    public void deleteById(Long id) {
        employeeRepository.deleteById(id);
    }
}

Step 6: Create the Controller Class

EmployeeController

Create a new Java class named EmployeeController in the com.example.hamcrestdemo package:

package com.example.hamcrestdemo;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/employees")
public class EmployeeController {

    private final EmployeeService employeeService;

    public EmployeeController(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @GetMapping
    public List<Employee> getAllEmployees() {
        return employeeService.findAll();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Employee> getEmployeeById(@PathVariable Long id) {
        return employeeService.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public Employee createEmployee(@RequestBody Employee employee) {
        return employeeService.save(employee);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Employee> updateEmployee(@PathVariable Long id, @RequestBody Employee employee) {
        return employeeService.findById(id)
                .map(existingEmployee -> {
                    employee.setId(existingEmployee.getId());
                    return ResponseEntity.ok(employeeService.save(employee));
                })
                .orElse(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteEmployee(@PathVariable Long id) {
        return employeeService.findById(id)
                .map(employee -> {
                    employeeService.deleteById(id);
                    return ResponseEntity.noContent().build();
                })
                .orElse(ResponseEntity.notFound().build());
    }
}

Step 7: Create the Main Application Class

Create a main application class named HamcrestDemoApplication in the com.example.hamcrestdemo package:

package com.example.hamcrestdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HamcrestDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(HamcrestDemoApplication.class, args);
    }
}

Explanation: The HamcrestDemoApplication class contains the main method, which is the entry point of the Spring Boot application. The @SpringBootApplication annotation is a convenience annotation that adds all the following:

  • @Configuration: Tags the class as a source of bean definitions for the application context.
  • @EnableAutoConfiguration: Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.
  • @ComponentScan: Tells Spring to look for other components, configurations, and services in the specified package.

Step 8: Create Hamcrest Test Classes

Test the EmployeeService with Hamcrest

Create a new test class named EmployeeServiceTest in the src/test/java/com/example/hamcrestdemo package:

package com.example.hamcrestdemo;

import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;
import java.util.Optional;

@SpringBootTest
public class EmployeeServiceTest {

    @Autowired
    private EmployeeService employeeService;

    @Test
    public void testFindAll() {
        List<Employee> employees = employeeService.findAll();
        MatcherAssert.assertThat(employees, Matchers.notNullValue());
        MatcherAssert.assertThat(employees, Matchers.empty());
    }

    @Test
    public void testSave() {
        Employee employee = new Employee("Amit Sharma", "[email protected]");
        Employee savedEmployee = employeeService.save(employee);
        MatcherAssert.assertThat(savedEmployee.getId(), Matchers.notNullValue());
        MatcherAssert.assertThat(savedEmployee.getName(), Matchers.is("Amit Sharma"));
    }

    @Test
    public void testFindById() {
        Employee employee = new Employee("Rohit Sharma", "[email protected]");
        Employee savedEmployee = employeeService.save

(employee);
        Optional<Employee> foundEmployee = employeeService.findById(savedEmployee.getId());
        MatcherAssert.assertThat(foundEmployee.isPresent(), Matchers.is(true));
        MatcherAssert.assertThat(foundEmployee.get().getName(), Matchers.is("Rohit Sharma"));
    }

    @Test
    public void testDeleteById() {
        Employee employee = new Employee("Suresh Kumar", "[email protected]");
        Employee savedEmployee = employeeService.save(employee);
        employeeService.deleteById(savedEmployee.getId());
        Optional<Employee> foundEmployee = employeeService.findById(savedEmployee.getId());
        MatcherAssert.assertThat(foundEmployee.isPresent(), Matchers.is(false));
    }
}

Explanation

Annotations and Class-Level Setup

  • @SpringBootTest: This annotation is used to load the complete application context, allowing us to test the Spring Boot application as a whole.

Dependency Injection

  • @Autowired private EmployeeService employeeService;: This injects the EmployeeService into the test class, allowing us to call its methods and test its functionality.

Test Methods and Assertions

  1. testFindAll Method

    @Test
    public void testFindAll() {
        List<Employee> employees = employeeService.findAll();
        MatcherAssert.assertThat(employees, Matchers.notNullValue());
        MatcherAssert.assertThat(employees, Matchers.empty());
    }
    
    • This method tests the findAll method of EmployeeService.
    • It retrieves all employees and asserts that the list is not null and is empty.
    • Hamcrest Assertions:
      • MatcherAssert.assertThat(employees, Matchers.notNullValue()): Asserts that the employees list is not null.
      • MatcherAssert.assertThat(employees, Matchers.empty()): Asserts that the employees list is empty.
  2. testSave Method

    @Test
    public void testSave() {
        Employee employee = new Employee("Amit Sharma", "[email protected]");
        Employee savedEmployee = employeeService.save(employee);
        MatcherAssert.assertThat(savedEmployee.getId(), Matchers.notNullValue());
        MatcherAssert.assertThat(savedEmployee.getName(), Matchers.is("Amit Sharma"));
    }
    
    • This method tests the save method of EmployeeService.
    • It creates a new Employee object, saves it, and asserts that the id of the saved employee is not null and the name is equal to "Amit Sharma".
    • Hamcrest Assertions:
      • MatcherAssert.assertThat(savedEmployee.getId(), Matchers.notNullValue()): Asserts that the id of the saved employee is not null.
      • MatcherAssert.assertThat(savedEmployee.getName(), Matchers.is("Amit Sharma")): Asserts that the name of the saved employee is "Amit Sharma".
  3. testFindById Method

    @Test
    public void testFindById() {
        Employee employee = new Employee("Rohit Sharma", "[email protected]");
        Employee savedEmployee = employeeService.save(employee);
        Optional<Employee> foundEmployee = employeeService.findById(savedEmployee.getId());
        MatcherAssert.assertThat(foundEmployee.isPresent(), Matchers.is(true));
        MatcherAssert.assertThat(foundEmployee.get().getName(), Matchers.is("Rohit Sharma"));
    }
    
    • This method tests the findById method of EmployeeService.
    • It creates and saves a new Employee object, retrieves it by id, and asserts that the employee is present and the name is equal to "Rohit Sharma".
    • Hamcrest Assertions:
      • MatcherAssert.assertThat(foundEmployee.isPresent(), Matchers.is(true)): Asserts that the employee is present.
      • MatcherAssert.assertThat(foundEmployee.get().getName(), Matchers.is("Rohit Sharma")): Asserts that the name of the found employee is "Rohit Sharma".
  4. testDeleteById Method

    @Test
    public void testDeleteById() {
        Employee employee = new Employee("Suresh Kumar", "[email protected]");
        Employee savedEmployee = employeeService.save(employee);
        employeeService.deleteById(savedEmployee.getId());
        Optional<Employee> foundEmployee = employeeService.findById(savedEmployee.getId());
        MatcherAssert.assertThat(foundEmployee.isPresent(), Matchers.is(false));
    }
    
    • This method tests the deleteById method of EmployeeService.
    • It creates and saves a new Employee object, deletes it by id, and asserts that the employee is not present.
    • Hamcrest Assertions:
      • MatcherAssert.assertThat(foundEmployee.isPresent(), Matchers.is(false)): Asserts that the employee is not present after deletion.

Step 9: Run the Tests

You can run the tests using your IDE or by executing the following command in the terminal:

mvn test

You should see an output indicating that all tests have passed successfully.

Conclusion

In this tutorial, we demonstrated how to integrate Hamcrest into a Spring Boot application and covered a wide range of assertions. We created a simple Spring Boot application with Employee entity, repository, service, and controller layers, and wrote Hamcrest test cases to test the service layer. 

By following these steps, you can efficiently use Hamcrest to write expressive and maintainable assertions, ensuring the correctness of your application.

Comments