header

Spring Boot Geb

BDD (Behavior-driven development) is a technique very similar to implement UAT (User Acceptance Testing) in a software project. Usually is a good idea to use BDD to reprecent how users can define application behaviour, so in that way you can represent user stories in test scenarios aka. feature testing. This time I am going to show you how integrate Geb to a Spring Boot application, Geb is a very powerful testing framework written in the Groovy programming language, which follows the BDD methodology. NOTE: If you need to know what tools you need to have installed in your computer in order to create a Spring Boot basic project, please refer my previous post: Spring Boot

Let’s start creating a new Spring Boot project with Webflux, Lombok and Thymeleaf as dependencies:

spring init --dependencies=webflux,lombok,thymeleaf --build=gradle --language=java spring-boot-geb

Here is the complete build.gradle file generated:

buildscript {
  ext {
    springBootVersion = '2.1.0.RELEASE'
  }
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
  }
}

apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.jos.dem.springboot.geb'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
	mavenCentral()
}

dependencies {
  implementation('org.springframework.boot:spring-boot-starter-webflux')
  implementation('org.springframework.boot:spring-boot-starter-thymeleaf')
  compileOnly('org.projectlombok:lombok')
  testImplementation('org.springframework.boot:spring-boot-starter-test')
  testImplementation('io.projectreactor:reactor-test')
}

Now add latest Groovy, Selenium Firefox Driver, Spock and WebDriverManager dependencies to your build.gradle file:

implementation('org.codehaus.groovy:groovy-all:2.5.0')
testImplementation('org.gebish:geb-spock:2.2')
testImplementation('org.seleniumhq.selenium:selenium-firefox-driver:3.141.59')
testImplementation('org.spockframework:spock-spring:1.1-groovy-2.4')
testImplementation("io.github.bonigarcia:webdrivermanager:2.2.5")

Now let’s create a simple POJO retrieve information from an end point using Spring WebFlux.

package com.jos.dem.springboot.geb.model;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.Data;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {

  private String nickname;
  private String email;

}

Lombok is a great tool to avoid boilerplate code, for knowing more please go here. Next step is to create methods to list, create and save persons:

package com.jos.dem.springboot.geb.controller;

import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;

import org.springframework.ui.Model;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;

import com.jos.dem.springboot.geb.model.Person;
import com.jos.dem.springboot.geb.service.PersonService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Controller
@RequestMapping("/persons")
public class PersonController {

  @Autowired
  private PersonService personService;

  private Logger log = LoggerFactory.getLogger(this.getClass());

  @RequestMapping(method=GET)
  public String persons(final Model model){
    log.info("Listing persons");
    model.addAttribute("persons", personService.getAll());
    return "persons/list";
  }

  @RequestMapping(method=GET ,value="create")
  public String create(final Model model){
    log.info("Creating person");
    model.addAttribute("person", new Person());
    return "persons/create";
  }

  @RequestMapping(method=POST)
  public String save(final Person person, final Model model){
    log.info("Saving person");
    personService.save(person);
    model.addAttribute("persons", personService.getAll());
    return "persons/list";
  }

}

In order to complete our project let’s create PersonService to get our person’s data:

package com.jos.dem.springboot.geb.service;

import com.jos.dem.springboot.geb.model.Person;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface PersonService {
  Flux<Person> getAll();
  Mono<Person> getByNickname(String nickname);
  void save(Person person);
}

Implementation:

package com.jos.dem.springboot.geb.service.impl;

import java.util.HashMap;
import java.util.Map;

import com.jos.dem.springboot.geb.model.Person;
import com.jos.dem.springboot.geb.service.PersonService;

import org.springframework.stereotype.Service;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Service
public class PersonServiceImpl implements PersonService {

  private Map<String, Person> persons = new HashMap<String, Person>();

  public Flux<Person> getAll(){
    return Flux.fromIterable(persons.values());
  }

  public Mono<Person> getByNickname(String nickname){
    return Mono.just(persons.get(nickname));
  }

  public void save(Person person){
    persons.put(person.getNickname(), person);
  }

}

Geb by convention will look for a src/test/resources/GebConfig.groovy file which contains web driver definition Firefox by default, another important configuration is reports directory where Geb saves the screenshots and HTML dumps at the end of each test:

import io.github.bonigarcia.wdm.FirefoxDriverManager
import org.openqa.selenium.firefox.FirefoxDriver

reportsDir = 'build/test-reports'

atCheckWaiting = true

driver = {
  FirefoxDriverManager.getInstance().setup()
  new FirefoxDriver()
}

Next step is to define page object, since is a good idea to separate page layout from logic behaviour:

package com.jos.dem.springboot.geb.pages

import geb.Page

class PersonList extends Page {

  static url = "persons/list"
  static at = { title == "Person List" }
  static content = {
  }

}

Now we can define our test step definition using Spock Framework

package com.jos.dem.springboot.geb

import geb.spock.GebReportingSpec
import com.jos.dem.springboot.geb.pages.PersonList

class CreatePersonSpec extends GebReportingSpec {

  void 'should create person'() {
    given:
      go "http://localhost:8080/persons/create"

    when:
      $("input", name: "nickname").value("josdem")
      $("input", name: "email").value("joseluis.delacruz@gmail.com")
      $("button", name: "submit").click()

    then:
      at PersonList
    }

}

This test represent happy path:

  • given: A url and go instruction that directs the web driver to the create person page.
  • when: Fills out name and email text fields in the form and clicks the submit button.
  • then: Is just making sure we going to the right place. We could add any other assertions.

Geb is using a JQuery like style to access to our html elements, Geb call it Navigator API.

To browse the project go here, to download the project:

git clone https://github.com/josdem/spring_boot_geb.git

To run the project with Gradle:

gradle bootRun

To test the project with Gradle:

gradle test

To run the project with Maven:

mvn spring-boot:run

To test the project with Maven:

mvn test

Return to the main article

comments powered by Disqus