Giter Club home page Giter Club logo

spring-testing's Introduction

The Practical Test Pyramid: Spring Boot Edition

Build Status

This repository contains a Spring Boot application with lots of test examples on different levels of the Test Pyramid. It shows an opinionated way to thoroughly test your spring application by demonstrating different types and levels of testing. You will find that some of the tests are duplicated along the test pyramid -- concepts that have already been tested in lower-level tests will be tested in more high-level tests. This contradicts the premise of the test pyramid. In this case it helps demonstrating different kinds of tests which is the main goal of this repository.

Read the Blog Post

This repository is part of a blog posts I wrote about test automation and the test pyramid. I highly recommend you read it to get a better feeling for the purpose of the different kinds of tests in this repository and how you can implement a reliable test suite for a Spring Boot application.

Get started

1. Set an API Key as Environment Variable

In order to run the service, you need to set the WEATHER_API_KEY environment variable to a valid API key retrieved from darksky.net openweathermap.org.

Note: in a previous version this example used darksky.net as the weather API. Since they've shut down their API for public access, we've since switched over to openweathermap.org

A simple way is to rename the env.sample file to .env, fill in your API key from openweathermap.org and source it before running your application:

source .env

2. Start a PostgreSQL database

The easiest way is to use the provided startDatabase.sh script. This script starts a Docker container which contains a database with the following configuration:

  • port: 15432
  • username: testuser
  • password: password
  • database name: postgres

If you don't want to use the script make sure to have a database with the same configuration or modify your application.properties.

3. Run the Application

Once you've provided the API key and started a PostgreSQL database you can run the application using

./gradlew bootRun

The application will start on port 8080 so you can send a sample request to http://localhost:8080/hello to see if you're up and running.

Application Architecture

 ╭┄┄┄┄┄┄┄╮      ┌──────────┐      ┌──────────┐
 ┆   ☁   ┆  ←→  │    ☕     │  ←→  │    💾     │
 ┆  Web  ┆ HTTP │  Spring  │      │ Database │
 ╰┄┄┄┄┄┄┄╯      │  Service │      └──────────┘
                └──────────┘
                     ↑ JSON/HTTP
                     ↓
                ┌──────────┐
                │    ☁     │
                │ Weather  │
                │   API    │
                └──────────┘

The sample application is almost as easy as it gets. It stores Persons in an in-memory database (using Spring Data) and provides a REST interface with three endpoints:

  • GET /hello: Returns "Hello World!". Always.
  • GET /hello/{lastname}: Looks up the person with lastname as its last name and returns "Hello {Firstname} {Lastname}" if that person is found.
  • GET /weather: Calls a downstream weather API via HTTP and returns a summary for the current weather conditions in Hamburg, Germany

Internal Architecture

The Spring Service itself has a pretty common internal architecture:

  • Controller classes provide REST endpoints and deal with HTTP requests and responses
  • Repository classes interface with the database and take care of writing and reading data to/from persistent storage
  • Client classes talk to other APIs, in our case it fetches JSON via HTTP from the openweathermap.org weather API
Request  ┌────────── Spring Service ───────────┐
 ─────────→ ┌─────────────┐    ┌─────────────┐ │   ┌─────────────┐
 ←───────── │  Controller │ ←→ │  Repository │←──→ │  Database   │
Response │  └─────────────┘    └─────────────┘ │   └─────────────┘
         │         ↓                           │
         │    ┌──────────┐                     │
         │    │  Client  │                     │
         │    └──────────┘                     │
         └─────────│───────────────────────────┘
                   │
                   ↓   
              ┌──────────┐
              │    ☁     │
              │ Weather  │
              │   API    │
              └──────────┘

Test Layers

The example applicationn shows different test layers according to the Test Pyramid.

      ╱╲
  End-to-End
    ╱────╲
   ╱ Inte-╲
  ╱ gration╲
 ╱──────────╲
╱   Unit     ╲
──────────────

The base of the pyramid is made up of unit tests. They should make the biggest part of your automated test suite.

The next layer, integration tests, test all places where your application serializes or deserializes data. Your service's REST API, Repositories or calling third-party services are good examples. This codebase contains example for all of these tests.

 ╭┄┄┄┄┄┄┄╮      ┌──────────┐      ┌──────────┐
 ┆   ☁   ┆  ←→  │    ☕     │  ←→  │    💾     │
 ┆  Web  ┆      │  Spring  │      │ Database │
 ╰┄┄┄┄┄┄┄╯      │  Service │      └──────────┘
                └──────────┘

  │    Controller     │      Repository      │
  └─── Integration ───┴──── Integration ─────┘

  │                                          │
  └────────────── Acceptance ────────────────┘               
 ┌─────────┐  ─┐
 │    ☁    │   │
 │ Weather │   │
 │   API   │   │
 │  Stub   │   │
 └─────────┘   │ Client
      ↑        │ Integration
      ↓        │ Test
 ┌──────────┐  │
 │    ☕     │  │
 │  Spring  │  │
 │  Service │  │
 └──────────┘ ─┘

Tools

You can find lots of different tools, frameworks and libraries being used in the different examples:

  • Spring Boot: application framework
  • JUnit: test runner
  • Hamcrest Matchers: assertions
  • Mockito: test doubles (mocks, stubs)
  • MockMVC: testing Spring MVC controllers
  • RestAssured: testing the service end to end via HTTP
  • Wiremock: provide HTTP stubs for downstream services

spring-testing's People

Contributors

hamvocke avatar lisajunger avatar lorenzobettini avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spring-testing's Issues

Mismatch in url from application.properties and WeatherClient

After the update the to openweathermap API, the /weather endpoint is not working. I think the reason is due to weather url.

In application.properties it is defined as:

weather.url = https://api.openweathermap.org/data/2.5/weather

And it is called in WeatherClient.java as:

var url = String.format("%s/data/2.5/weather?q=%s&appid=%s", weatherServiceUrl, CITY, weatherServiceApiKey);

The part /data/2.5/weather will be duplicated. A simple fix is to define it in application.properties as:

weather.url = https://api.openweathermap.org

After fixing it, the existing tests and the /weather endpoint were working for me.

By the way, is /src/main/resources/application-int.properties still used? It still contains a reference to the darksky API.

why PersonRepositoryIntegrationTest doesn't fail with h2 dependency with runtimeOnly gradle scope?

hi. Thanks for the interesting article about testing.

I was reading the following text from your article "I've defined H2 as a test dependency in the build.gradle file. The application.properties in the test directory doesn't define any spring.datasource properties. This tells Spring Data to use an in-memory database. As it finds H2 on the classpath it simply uses H2 when running our tests."

And I see the following:

  1. in article h2 is presented as test dependency but actually on code I see in build.gradle runtimeOnly scope.
  2. can you please explain why runtimeOnly 'com.h2database:h2' still works when I run PersonRepositoryIntegrationTest ? I thought that h2 is in runtime classpath and the test should fail due to dependencies. But it doesn't.

Consider removing all entries from the database before the test is executed

To make sure you don't run into any trouble (e.g. a corrupt state) it's always a good idea to remove any existing data before integration tests and e2e tests are executed.

From my experience it's a bit frustrating to do this in every test and for every entity that is handled by the tests, as you have to autowire each Repository and call the deleteAll() method. Fortunately there's SQL and Spring provides the @org.springframework.test.context.jdbc.Sql annotation, which allows you to execute SQL before a test.

So you can create a file containing nothing else than delete statements and have this executed before each test.

delete-all.sql

delete from Person;

SomeIntegrationTest.java

//... package, imports etc.

@Sql(scripts ="/delete-all.sql")
public class SomeIntegrationTest { }

Not possible to get DarkSky Api key anymore

From what I see, unfortunately, it is not possible to get an API Key from DarkSky anymore. They are no longer accepting new signups. Any idea of how can we fix this issue? Thanks!

How are the tests run/executed?

I'm trying to figure out how to simply execute the test cases. I don't see a junit dependency with "test" scope. I've tried gradlew test and also through the IDE. What's the process to get the test cases going?

Attempts to run all the test cases directly from the IDE throw the following error:

image

JUnit Errors - You cannot use argument matchers outside of verification or stubbing - NullPointerException

Not sure why, but six of the tests fail.

org.mockito.exceptions.misusing.InvalidUseOfMatchersException

Exemple, misplaced or misused argument matcher detected here:
-> at example.ExampleControllerTest.shouldTellIfPersonIsUnknown(ExampleControllerTest.java:56)"

This method

@Test
  public void shouldTellIfPersonIsUnknown() throws Exception {
      given(personRepository.findByLastName(anyString())).willReturn(Optional.empty());

      var greeting = subject.hello("Pan");

      assertThat(greeting, is("Who is this 'Pan' you're talking about?"));
  }

Include authentication.

Would be nice to have demo of how to integration test controllers when authentication is in play.

Handle source .env and ./startDatabase automatically

Hi Ham,

I'm making videos about the test reference, and I think it is a distraction that every time I clone the repository, I need to copy example.env to .env, source it, and start the database. For my audience, I want to be able to focus on the tests, not the setup. Is there a theoretical reason why these steps can't be in the gradle build? Thanks again for this--it is really helping us.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.