spring boot tutorial

Spring Boot Tutorial

During our last Innovation Day, we sat down to develop a new web framework which we can use for projects. Below you can see the criteria that we took under consideration when working on this.

  • REST API / JSON / Swagger
  • Test support
  • Validation/conversion
  • Database/transactions/evolutions
  • Build tool
  • Upgrades and support
  • Lightweight
  • Documentation
  • Security
  • Config
  • I18N

PS: This tutorial is an opinionated guide on how to build a simple web application using Spring Boot, that demonstrates the selected topics. The git repository of the finished tutorial can be found here.

Application Requirements

The example application will be a simple user management application; it has to meet the following requirements:

  • CRUD Rest API for storing users
  • User Roles
    • Admins should be able to edit user data
    • Regular users should only be able to view user data
  • User validation
    • Phone numbers should only contain numbers, white-space characters and the following special characters: + ( )
    • Passwords should be at least 4 characters
    • Email addresses should be valid
  • Passwords should be stored securely
  • The application should use basic HTTP authentication

Architecture

The example application uses a simple layered architecture:

architecture

This architecture is extremely common. To make it work, you have to make sure each layer only communicates with the layer directly above or below it (hierarchical structure). This makes sure each layer only depends on the adjacent layer. When you change the API of one of the layers you only need to update the layers below or above it. One common example is when you change your database vendor, you should only need to update your persistence layer, not any of the other layers. This architecture also allows you to have multiple implementations of one layer, a common example is when you have multiple implementations of your presentation layer, e.g. a JSON service and an HTML front-end.

Presentation Layer

The presentation layer handles all HTTP requests, translates JSON parameters to objects and back, and handles authentication. It does not handle any of the business logic, it simply handles the request, authenticates it and passes it to the business layer.

Business Layer

The business layer handles all the business logic. Validation and authorization are also done in the this layer. The primary reason is that validation and authorization are part of the business logic, e.g.: a phone number may only contain certain characters and only administrators are allowed to edit user accounts. A more practical reason is that a presentation layer component might use multiple business layer components. If we put the validation and authorization in the presentation layer we would need to re-implement validation and authorization in each presentation layer component.

Persistence Layer

This layer contains all the storage logic(database queries), and translates business objects from and to database rows.

Database Layer

The actual database(e.g.: MySQL, Postgres, H2, Redis or MongoDB).

Implementation

An example implementation of a layered architecture using Spring boot can look like this:

implementation

Web requests are handled in controllers, services contain the business logic and repositories handle persistence. The database layer can contain one or more databases.

Spring has annotations for services, controllers and repositories, but it does not stop you from skipping a layer in the architecture. Keeping the architecture clean is the developers responsibility! These standard annotations provide a pretty good guideline of where each class belongs, but not all classes that you add to your application are controllers, services or repositories. When adding a new class, try to fit it in one layer, try not to mix functionality of multiple layers; a class that handles HTTP errors should not contain business logic).

Directory Structure

Below is the directory structure of the project. This tutorial only discusses the java classes, but the example repository also contains a simple front end application. You should be able to test the application without the front-end by using Swagger UI(discussed in a later section).

Directory Structure
├── pom.xml
└── src
└── main
├── java
│ └── nl
│ └── anchormen
│ └── usermanager
│ ├── SecurityConfig.java
│ ├── user
│ │ ├── User.java
│ │ ├── UserRepository.java
│ │ ├── UserRole.java
│ │ └── UserService.java
│ ├── UserApp.java
│ └── web
│ ├── ErrorHandler.java
│ └── UserController.java
└── resources
├── application.properties
└── public
├── index.html
├── usermanager.css
├── usermanager.js

Maven Config

Below is the maven configuration. Spring Boot uses a parent pom to provide sensible defaults and manage dependency versions.

pom.xml
<?xml version="1.0" encoding="UTF-8"?&gt;
<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>
     <groupId>nl.anchormen.usermanager</groupId>
     <artifactId>usermanager</artifactId>
     <version>1.0-SNAPSHOT</version>
     <packaging>jar</packaging>

     <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <maven.compiler.source>1.8</maven.compiler.source>
       <maven.compiler.target>1.8</maven.compiler.target>
     </properties>

     <!-- Provides sensible defaults for you spring boot project and manages
       dependency versions. -->
     <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.0.1.RELEASE</version>
     </parent>

     <dependencies>
       <!-- Spring boot with web development dependencies. -->
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
       </dependency>

       <!-- Database integration using Spring Data. -->
       <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-jpa</artifactId>
       </dependency>

       <!-- Simple file based or in memory database. Handy for testing, but a
         file based database can also be used for a small application. -->
       <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
       </dependency>

       <!-- Spring security framework that provides authentication and authorization. -->
       <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-security</artifactId>
       </dependency>

       <!-- Springfox is currently the best known swagger integration library
         for Spring. It also provides easy swagger UI integration. -->
       <dependency>
         <groupId>io.springfox</groupId>
         <artifactId>springfox-swagger2</artifactId>
         <version>2.7.0</version>
       </dependency>
       <dependency>
         <groupId>io.springfox</groupId>
         <artifactId>springfox-swagger-ui</artifactId>
         <version>2.7.0</version>
         <scope>compile</scope>
       </dependency>

       <!-- For unit and integrations tests, it includes mockito and BDDMockito. -->
       <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
       </dependency>

       <!-- Required for authentication testing. -->
       <dependency>
         <groupId>org.springframework.security</groupId>
         <artifactId>spring-security-test</artifactId>
       </dependency>

       <!-- Provides features for making development easier, e.g. quickly reloads
         the application when files change like JRebel. -->
       <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-devtools</artifactId>
       </dependency>
   </dependencies>
   <build>
       <plugins>
         <!-- For packaging executable jar or war files. -->
         <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
         <!-- For running integration tests. -->
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-failsafe-plugin</artifactId>
           <executions>
             <execution>
               <goals>
                 <goal>integration-test</goal>
                 <goal>verify</goal>
               </goals>
             </execution>
           </executions>
         </plugin>
      </plugins>
    </build>
</project>

Spring configuration

This is the most basic configuration for a spring boot application, when you start the application, Spring wires together all the required components and starts an embedded Tomcat server.

@SpringBootApplication
public class UserApp
{

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

// .. left out for clarity, more will be added in later sections.

}

This main application class is annotated with @SpringBootApplication, this annotation combines multiple Spring annotations: @EnableAutoConfiguration, @ComponentScan, @Configuration.

@EnableAutoconfiguration enables the convention over configuration features of the spring framework, it automatically configures spring by scanning the classpath for libraries.

The @ComponentScan and @Confuration annotation will be discussed in the dependency injection section.

Dependency injection

Spring has good support for DI(dependency injection), it wires together all your objects when the application boots. We will only cover the most commonly used DI annotations. Examples of the annotations can be seen in later sections.

@AutoWired

This annotation automatically finds the right Spring Bean and inserts it into the object. Spring beans are Java objects that are managed by the Spring framework.

Beans can be injected into fields, methods and constructors. The preferred method for initializing objects is by inserting dependencies in the constructor.

@Component

This annotation tells spring that it needs to add an instance of this class as a Spring bean. Classes with this annotation can be auto-wired.

Specialist classes for this annotation exist: @Repository@Service and @Controller, these annotations will be discussed later sections.

@Configuration and @Bean

A class with the @Configuration annotation can contain methods with the @Bean annotation. The return values of these methods will be registered as Spring beans and can be auto-wired.

@ComponentScan

This annotation tells spring to scan for classes annotated with @Component. In our application this is included in the form of a @SpringBootApplication annotation.

Security configuration

Spring security has its own configuration class that extends WebSecurityConfigurerAdapter.

nl.anchormen.usermanager.SecurityConfig
// Enables method level authorization on the service layer. Will be discussed in the service layer section.
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{

@Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
// A REST API should not have any state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// Authorize all requests
.authorizeRequests()
// except these paths, they contain public static content.
.antMatchers("/", "/*.html", "/*.js", "/*.css",
// Urls for static content for the h2 database.
"/h2/**", "/console/**",
// Urls for static content for swagger.
"/v2/api-docs", "/v2/swagger-ui.html", "/swagger-resources/**", "/webjars/springfox-swagger-ui/**")
.permitAll()
// All other requests should be authenticated
.anyRequest().authenticated()
// using basic HTTP authentication.
.and().httpBasic()
// allow requests from frames of the same origin, required for the h2 tooling
.and().headers().frameOptions().sameOrigin();

// Write security error responses, the ErrorHandler will be explained in a later section
http.exceptionHandling().authenticationEntryPoint(
(request, response, exception) -> ErrorHandler.writeError(response, "Authentication Error", HttpStatus.UNAUTHORIZED))
.accessDeniedHandler(
(request, response, exception) -> ErrorHandler.writeError(response, "Forbidden", HttpStatus.FORBIDDEN));
}
/**
* Register bcrypt as the default password encoder using the @Bean annotation
* @return
*/
@Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder(6);
}

/**
* Spring can use a UserDetailsService for authenticating users, we register one using the @Bean annotation.
*
* This Spring Bean requires a UserService, spring will automatically search for it in the other beans.
*
* @param userService
* @return
*/
@Bean
public UserDetailsService userDetailsService(UserService userService)
{
return email ->  {
User user = userService.findByEmail(email);
if (user == null)
{
throw new UsernameNotFoundException(email);
}
List<GrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority(user.getUserRole().getName()));
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getEncodedPassword(),
authorities);
};
}

/**
* Configure Spring Security to use our UserDetailsService and PasswordEncoder, the required objects will be auto-wired into this method.
*
* @param auth
* @param userDetailsService
* @param passwordEncoder
* @throws Exception
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) throws Exception
{
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}

}

This is only the authentication part of the security layer. Authorization will handled later on, in the business layer.

Database Layer

The application uses H2, this a simple file based database. It requires some configuration:

src/main/resources/application.properties
# H2 Console, a simple GUI for managing the database. Not required, but useful for development.
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.h2.console.username=sa

# H2 Datasource
spring.datasource.url=jdbc:h2:file:~/test
spring.datasource.username=sa
spring.datasource.password=
# Automatically update tables when persistence objects have changed.
spring.jpa.hibernate.ddl-auto=update

Persistence Layer

The persistence layer is implemented using Spring’s CrudRepository. It provides many standard methods like save, findById and findAll. One of the more fancy features of the CrudRepository, is the creation of queries based on the methods name.

nl.anchormen.usermanager.user.UserRepository
// @Repository allows Spring to detect this class as a bean(@Repository is a special type of @Component).
// Annotating with @Repository in stead of @Component translates platform specific persistence exceptions to Spring exceptions.
@Repository
public interface UserRepository extends CrudRepository<User, Long>
{
/**
* Spring will automatically generate a query for finding a user by its email address
* @param email
* @return
*/
User findByEmail(String email);
}

This is the entire Persistence layer for our application. This works because our application is a very simple CRUD application, when we need more complicated methods we can use the @Query annotation to write custom queries.

Business Layer

The business layer is implemented as a service and two business objects.

Business Objects

The business object contains the following javax.validations annotations that the service can use for validation:

  • @Email requires a valid email address
  • @NotEmpty the String is not allowed to be null or empty
  • @NotNull Object is not allowed to be null

The @JsonIgnore annotation is to disable JSON serialization for this property. This prevents the hashed password from being sent to the client.

@GeneratedValue(strategy = GenerationType.AUTO) tells the database how to generate ids.

package nl.anchormen.usermanager.user.User
@Entity
public class User
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@NotEmpty
@Email
private String email;
@Pattern(regexp="^(\\s|\\d|[\\+\\(\\)\\-])*$", message="Invalid phonenumber")
private String phoneNumber;

@JsonIgnore
@NotEmpty
private String encodedPassword;
@Enumerated(EnumType.STRING)
@NotNull
private UserRole UserRole;

public Long getId()
{
return id;
}

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

public String getEmail()
{
return email;
}

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

public String getPhoneNumber()
{
return phoneNumber;
}

public void setPhoneNumber(String phoneNumber)
{
this.phoneNumber = phoneNumber;
}

public UserRole getUserRole()
{
return UserRole;
}

public void setUserRole(UserRole userRole)
{
UserRole = userRole;
}

public String getEncodedPassword()
{
return encodedPassword;
}

public void setEncodedPassword(String encodedPassword)
{
this.encodedPassword = encodedPassword;
}

@Override
public int hashCode()
{
// generated
}

@Override
public boolean equals(Object obj)
{
// generated
}

}

The UserRole Object:

nl.anchormen.usermanager.user.UserRole
public enum UserRole
{
USER("ROLE_USER"), ADMIN("ROLE_ADMIN");

private final String name;

private UserRole(String name)
{
this.name = name;
}

// Use name property when translating to json, this requires the name needs to be unique.
@JsonValue
public String getName()
{
return name;
}

}

One flaw of this annotation-based approach is that the layers leak into each other. This approach is chosen because it is easy, you only need to change one file when you change the business object.

The biggest downside of this approach is that you might end up with a class that is more annotation than code. Another downside is that your business layer depends on a persistence library and a JSON serialization library.

If you want a good separation in your business objects you can replace the annotations with an XML file per layer. However, the downside of this approach is that you need to change a lot of files when you change a business object.

Service

To enable validation on the service layer we need to add some extra configuration on the main application.

nl.anchormen.usermanager.UserApp
@SpringBootApplication
public class UserApp
{

// ... left out for clarity

/**
* Enable javax.validation
* @return
*/
@Bean
public Validator localValidatorFactoryBean()
{
return new LocalValidatorFactoryBean();
}

// ... left out for clarity

}

The service implements most of the business logic, it also contains a convenience method to fill the applications with some data on first boot.

package nl.anchormen.usermanager.user.UserService
// @Validated is part of the javax.validation integration, this allows you to validate business objects using annotations
@Validated
// @Service allows Spring to detect this class as a bean(@Service is a special type of @Component).
// The Service annotation does not have implementation differences with @Component(yet).
@Service
public class UserService
{

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;

// Inject both the user repository and the password encoder  using autowired
@Autowired
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder)
{
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}

public User findByEmail(String email)
{
return userRepository.findByEmail(email);
}

/**
* Fill with stored password.
*
* @param user
*/
public void fillPassword(User user)
{
if (user.getId() != null && user.getEncodedPassword() == null)
{
userRepository.findById(user.getId())
.ifPresent(oldValue -> user.setEncodedPassword(oldValue.getEncodedPassword()));
}
}

/**
* The @PreAuthorize checks if the user has the correct roles before the method
* is executed. It throws an Exception if the user does not have the right
* permissions, we configured an accessDeniedHandler in the security configuration to
* handle this
*/
@PreAuthorize("hasRole('ADMIN')")
public User save(@Valid User user)
{
return userRepository.save(user);
}

public Iterable<User> getAll()
{
return userRepository.findAll();
}

@PreAuthorize("hasRole('ADMIN')")
public void changePassword(User user, @Valid @Size(min = 4) String password)
{
user.setEncodedPassword(passwordEncoder.encode(password));
}

// This annotation causes database interactions in this method to be executed as a transaction.
@Transactional
@PreAuthorize("hasRole('ADMIN')")
// javax.validation annotations can also be used on method parameters
public boolean delete(@Valid @NotNull Long id)
{
if (!userRepository.existsById(id))
{
return false;
}
userRepository.deleteById(id);
return true;
}

/**
* This method fills the database with some test data if the database it is empty.
*/
@PostConstruct
public void init()
{
if (getAll().iterator().hasNext())
{
return;
}

String[] testUserNames = new String[] { "pietje", "jantje", "klaas" };
for (int i = 0; i < testUserNames.length; i++)
{
String name = testUserNames[i];
User user = new User();
user.setEmail(name + "@test.nl");
user.setUserRole(UserRole.USER);
changePassword(user, name);
user.setPhoneNumber(String.format("06123456%02d", i));
save(user);
}

User admin = new User();
admin.setEmail("admin@test.nl");
changePassword(admin, "admin");
admin.setUserRole(UserRole.ADMIN);
save(admin);
}

}

When validation fails a ConstraintViolationException is thrown, we handle this exception in the Presentation layer.

Transactions are done using the @Transactional annotation. This annotation is not used on the persistence layer, but on methods of the business layer. This might seem like the wrong place for defining transactions, but this is done because the service layer might call multiple methods of our UserRepository that might be part of the same transaction.  You could argue that what is part of a transaction is part of the business logic, the persistence layer doesn’t know which operations belong together, it only knows how to perform the operations.

Presentation Layer

The Presentation layer handles HTTP requests, and translates objects from and to JSON. Spring allows you to register controller beans to handle web request.

nl.anchormen.usermanager.web.UserController
// A @Controller is a special type of @component that can handle web requests.
// To handle web requests you must use the @Controller annotation, @Component will not work.
// The RestController is a special type of controller for rest applications.
@RestController
public class UserController
{
private final UserService userService;

@Autowired
public UserController(UserService userService)
{
this.userService = userService;
}

// Swagger documentation, see the swagger section for more details.
@ApiOperation("get all users")
// Maps a list of users to /users, the url for the local application is localhost:8080/users
@GetMapping("/users")
public Iterable<User> users()
{
return userService.getAll();

}

@ApiOperation("save a user")
@PostMapping("/user")
/**
* We do not send the hashed password back and forth to the client, but the
* application needs to be able to change the password. The SaveUserDto allows
* the client to send a new password if it wants to change the password, or
* leave it null when the password should stay the same.
*
* @param saveUserDto
* @return
*/
public User saveUser(@RequestBody SaveUserDto saveUserDto)
{
User toSave = saveUserDto.user;
if (saveUserDto.password == null)
{
// We want to save the user with its old password.
userService.fillPassword(toSave);
} else
{
userService.changePassword(toSave, saveUserDto.password);
}

return userService.save(toSave);
}

@ApiOperation("delete a user")
@DeleteMapping("/user/{userId}")
public Long deleteUser(@PathVariable(value = "userId") Long userId, HttpServletResponse res)
{
if (userService.delete(userId))
{
return userId;
}
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
return -1L;
}

@ApiOperation("get all existing user roles")
@GetMapping("/userRoles")
public Iterable<UserRole> userRoles()
{
return Arrays.asList(UserRole.values());
}

/**
* Special object for saving a user with an optional password.
*/
static class SaveUserDto
{
private User user;
private String password;

public User getUser()
{
return user;
}

public void setUser(User user)
{
this.user = user;
}

public String getPassword()
{
return password;
}

public void setPassword(String password)
{
this.password = password;
}

}
}

Building the application

The maven build command is:

build the application
mvn package

Running the Application

Running the application from the command line:

Running the application
java -jar target/usermanager-1.0-SNAPSHOT.jar

You can view the application here.

Testing

The application will have two types of tests – unit tests and integration tests. Unit tests only test one small part of the application. On the other hand, integration tests integrate multiple systems and test them together. All the unit tests will be run when we build the application. It takes a lot longer to run integration tests, they need to be run separately.

Unit Testing

This test uses BDD Mockito. BDD defines scenarios in natural language using a predefined format. For example, given a repository that contains a user, when I search the service by email, then I should be able to find that user. This test description results in the following test code:

nl.anchormen.usermanager.UserServiceTest
public class UserServiceTest
{
@Test
public void userServiceTest()
{
User sjaak = new User();
sjaak.setId(1L);
sjaak.setEmail("sjaak@sjaak.nl");
UserRepository userRepository = mock(UserRepository.class);
//given
given(userRepository.findByEmail("sjaak@sjaak.nl")).willReturn(sjaak);
UserService userService = new UserService(userRepository, new BCryptPasswordEncoder(4));

//when
User sjaakFromService = userService.findByEmail("sjaak@sjaak.nl");

//then
assertEquals(sjaak, sjaakFromService);
}
}

Notice the given and willReturn methods. BDDMockito provides methods that match the language of BDD more closely than standard Mockito.

You can run all unit tests with the following command:

run unit tests
mvn test

Integration Testing

Integration tests wire together multiple components before testing. We do this by wiring an entire spring application with an in-memory database. The advantage of this type of testing is that it is a lot closer to the real environment than the unit tests, the disadvantage is that they are a lot slower(this is why we do not run them when packaging).

Testing the Service

The service test uses a userService that is autowired in the same way it would be autowired in the real application. The @WithCustomMockUser annotation will be explained in a later section.

nl.anchormen.usermanager.UserServiceIt
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceIt
{

@Autowired
UserService userService;

@Test
public void testFindUser()
{
User user = userService.findByEmail("pietje@test.nl");
assertEquals(user.getEmail(), "pietje@test.nl");
}

@Test
@WithCustomMockUser
public void testSaveInvalidUser()
{
User user = new User();
try {
userService.save(user);
fail("Expected ConstraintViolationException");
}
catch(ConstraintViolationException e) {
}
}

}

Mocking the User

Spring allows you to define custom annotations to simplify testing. The annotation below allows you to test the service with an authenticated user.

nl.anchormen.usermanager.test.WithCustomMockUser
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithCustomMockUser
{
String username() default "admin@test.nl";
String password() default "admin";
String[] roles() default {"USER_ROLE"};
}

The class below handles the custom annotation:

nl.anchormen.usermanager.test.WithMockCustomUserSecurityContextFactory
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithCustomMockUser>
{

@Autowired
PasswordEncoder PasswordEncoder;

@Override
public SecurityContext createSecurityContext(WithCustomMockUser annotation)
{
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
Authentication authentication = new UsernamePasswordAuthenticationToken(annotation.username(), annotation.password());
securityContext.setAuthentication(authentication);
return securityContext;
}

}

Testing the Controller

The tests below test the controller using the MockMvc class. This allows you to test how the controller responds to httpRequests.

nl.anchormen.usermanager.UserControllerIt
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerIt
{
@Autowired
private MockMvc mockMvc;

@Test
public void testGetUsersUnauthorized() throws Exception {
this.mockMvc.perform(get("/users")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isUnauthorized());
}

@Test
public void testGetUsers() throws Exception {
this.mockMvc.perform(get("/users")
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("pietje@test.nl:pietje".getBytes()))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}

Run integration tests

mvn verify

Swagger

Swagger only requires a little bit of configuration. The ApiInfo class allows you to provide some general documentation for your Swagger API.

nl.anchormen.usermanager.UserApp
@EnableSwagger2
@SpringBootApplication
public class UserApp
{

// .. left out for clariy
/**
* Swagger config
*
* endpoints:
* http://localhost:8080/v2/api-docs
* http://localhost:8080/swagger-ui.html
*
*/
@Bean
public Docket api()
{
ApiInfo apiInfo = new ApiInfo("User Manager API", "API for managing users", "", "",
new Contact("pieter", "", ""), "", "", Collections.emptyList());
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
//exclude default spring error page
.paths((s) -> !s.equals("/error"))
.build()
.apiInfo(apiInfo)
.securitySchemes(Arrays.asList(new BasicAuth("basic")));
}
}

When you want to add some documentation to the URL endpoints you can add a @Apioperation annotation on your controller methods:

@ApiOperation("get all existing user roles")
@GetMapping("/userRoles")
public Iterable<UserRole> userRoles()

This is all that is required for configuring swagger. You can download the swagger file on :

http://localhost:8080/v2/api-docs

Or view a more user friendly version that uses Swagger-UI.

http://localhost:8080/swagger-ui.html

H2 provides a simple web client to try browse the database

http://localhost:8080/h2

Like this article and want to stay updated of more news and events?
Then sign up for our newsletter!

Don't miss out!

Subscribe to our newsletter and stay up to date with our latest articles and events!

Subscribe now

* These fields are required.