first commit: pure unit tests and cucumber bdd tests with database integration

This commit is contained in:
Sander Hautvast 2020-01-22 16:01:50 +01:00
commit 46eec5da55
16 changed files with 585 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
.idea/
Reports/
target/
*.iml
.DS_Store

91
pom.xml Normal file
View file

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>nl.sander</groupId>
<artifactId>testautomatisering</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>testautomatisering</name>
<description>POC van dynamische (testgedreven) manier om met dependency injection van mocks om te gaan</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>8</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-core</artifactId>
<version>5.0.0-RC4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>5.0.0-RC4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java8</artifactId>
<version>5.0.0-RC4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>5.0.0-RC4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M4</version>
<configuration>
<includes>
<include>**/*Tests.java</include>
<include>**/*Test.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,118 @@
package nl.sander.testautomation.cars;
import java.util.Objects;
public class Car {
private long id;
private String brand;
private String model;
private String color;
private int year;
public static CarBuilder builder(){
return new CarBuilder();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return id == car.id &&
year == car.year &&
brand.equals(car.brand) &&
model.equals(car.model) &&
color.equals(car.color);
}
@Override
public int hashCode() {
return Objects.hash(id, brand, model, color, year);
}
@Override
public String toString() {
return "Car{" +
"id=" + id +
", brand='" + brand + '\'' +
", model='" + model + '\'' +
", color='" + color + '\'' +
", year=" + year +
'}';
}
public static class CarBuilder{
private final Car car = new Car();
public CarBuilder withId(long id){
car.setId(id);
return this;
}
public CarBuilder withBrand(String brand){
car.setBrand(brand);
return this;
}
public CarBuilder withModel(String model){
car.setModel(model);
return this;
}
public CarBuilder withColor(String color){
car.setColor(color);
return this;
}
public CarBuilder withYear(int year){
car.year=year;
return this;
}
public Car build(){
return car;
}
}
}

View file

@ -0,0 +1,13 @@
package nl.sander.testautomation.cars;
import java.sql.SQLException;
import java.util.List;
public interface CarDao {
Car getCar(long carId) throws SQLException;
List<Car> getAllCars() throws SQLException;
void store(Car car) throws SQLException;
}

View file

@ -0,0 +1,12 @@
package nl.sander.testautomation.cars;
import com.google.inject.AbstractModule;
public class CarModule extends AbstractModule {
@Override
protected void configure(){
bind(CarDao.class).to(JdbcCarDao.class);
bind(CarService.class).toInstance(new CarService());
}
}

View file

@ -0,0 +1,19 @@
package nl.sander.testautomation.cars;
import javax.inject.Inject;
import java.sql.SQLException;
import java.util.Optional;
public class CarService {
@Inject
private CarDao carDao;
public Optional<Car> getCar(long carId) {
try {
return Optional.of(carDao.getCar(carId));
} catch (SQLException e) {
return Optional.empty();
}
}
}

View file

@ -0,0 +1,74 @@
package nl.sander.testautomation.cars;
import nl.sander.testautomation.database.Database;
import javax.inject.Inject;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class JdbcCarDao implements CarDao {
@Inject
private Database database;
@Override
public Car getCar(long carId) throws SQLException {
try (Connection connection = createConnection()) {
PreparedStatement statement = connection.prepareStatement("select brand, model, color, year from cars where id = ?");
statement.setLong(1, carId);
ResultSet result = statement.executeQuery();
if (result.next()) {
Car car = new Car();
car.setId(carId);
car.setBrand(result.getString("brand"));
car.setModel(result.getString("model"));
car.setColor(result.getString("color"));
car.setYear(result.getInt("year"));
return car;
} else {
throw new IllegalArgumentException("no car found with id " + carId);
}
}
}
public void store(Car car) throws SQLException {
try (Connection connection = createConnection()) {
PreparedStatement statement = connection.prepareStatement("insert into cars (id, brand, model, color, year) " +
"values(?,?,?,?,?)");
statement.setLong(1, car.getId());
statement.setString(2, car.getBrand());
statement.setString(3, car.getModel());
statement.setString(4, car.getColor());
statement.setInt(5, car.getYear());
statement.executeUpdate();
}
}
@Override
public List<Car> getAllCars() throws SQLException {
try (Connection connection = createConnection()) {
PreparedStatement statement = connection.prepareStatement("select id, brand, model, color, year from cars");
ResultSet result = statement.executeQuery();
List<Car> cars = new ArrayList<>();
while (result.next()) {
cars.add(Car.builder()
.withId(result.getLong("id"))
.withBrand(result.getString("brand"))
.withModel(result.getString("model"))
.withColor(result.getString("color"))
.withYear(result.getInt("year"))
.build());
}
return cars;
}
}
private Connection createConnection() throws SQLException {
return new Database().getConnection();
}
}

View file

@ -0,0 +1,18 @@
package nl.sander.testautomation.database;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Database {
private final Connection connection;
public Database() throws SQLException {
this.connection = DriverManager.getConnection("jdbc:h2:~/test", "sa", "");
}
public Connection getConnection() throws SQLException {
return connection;
}
}

View file

@ -0,0 +1,16 @@
package nl.sander.testautomation;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(
strict = true,
features = {"src/test/java/nl/sander/testautomation/tests"},
glue = {"nl.sander.testautomation.steps"},
plugin = {"pretty", "html:Reports/cucumber-pretty"}
)
public class CucumberTests {
}

View file

@ -0,0 +1,7 @@
package nl.sander.testautomation;
public enum Mode {
UNIT_TEST,
UNIT_INTEGRATION_TEST,
DATABASE_INTEGRATION_TEST
}

View file

@ -0,0 +1,29 @@
package nl.sander.testautomation;
import com.google.inject.Guice;
import com.google.inject.Injector;
import nl.sander.testautomation.cars.CarModule;
import static com.google.inject.util.Modules.override;
public interface UnitTest {
Injector injector = Guice.createInjector(
override(new CarModule()).with(new UnitTestModule()));
default <T> T classToTest(Class<T> type) {
try {
T instance = type.newInstance();
injector.injectMembers(instance);
return instance;
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
default <T> T mock(Class<T> type) {
return injector.getInstance(type);
}
}

View file

@ -0,0 +1,28 @@
package nl.sander.testautomation;
import com.google.inject.AbstractModule;
import org.mockito.Mockito;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
/**
* mocks all classes in the SUT
*
* Works but could be optimized by only searching for types/annotations
*/
public class UnitTestModule extends AbstractModule {
@Override
@SuppressWarnings("unchecked")
protected void configure() {
new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forPackage("nl.sander.testautomation.cars"))
.setScanners(new SubTypesScanner(false))).getSubTypesOf(Object.class)
.stream()
.map(Class.class::cast)
.forEach(c -> bind(c).toInstance(Mockito.mock(c)));
}
}

View file

@ -0,0 +1,26 @@
package nl.sander.testautomation.steps;
import io.cucumber.java.Before;
import nl.sander.testautomation.Mode;
public class StepDefinitions {
public static Mode testMode;
@Before(value = "@DatabaseIntegrationTest", order = 1)
public void databaseIntegrationTest() {
testMode = Mode.DATABASE_INTEGRATION_TEST;
}
// @Before(value = "@UnitTest", order = 2)
// public void unitTest() {
// testMode = Mode.UNIT_TEST;
// }
//
// @Before(value = "@UnitIntegrationTest", order = 3)
// public void withDatabase() {
// testMode = Mode.UNIT_INTEGRATION_TEST;
// }
}

View file

@ -0,0 +1,89 @@
package nl.sander.testautomation.steps.cars;
import com.google.inject.Guice;
import com.google.inject.Injector;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java8.En;
import nl.sander.testautomation.Mode;
import nl.sander.testautomation.cars.Car;
import nl.sander.testautomation.cars.CarDao;
import nl.sander.testautomation.cars.CarModule;
import nl.sander.testautomation.database.Database;
import nl.sander.testautomation.steps.StepDefinitions;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.junit.Assert.assertTrue;
public class DatabaseStepDefinitions implements En {
private CarDao carDao;
public DatabaseStepDefinitions() {
When("^a user enters a car with id (\\d+?): a (.+?) (.+?) (.+?), built in (\\d+?)$", (Long id, String brand, String model, String color, Integer year) -> {
Car car = Car.builder()
.withId(id)
.withBrand(brand)
.withModel(model)
.withColor(color)
.withYear(year).build();
if (StepDefinitions.testMode == Mode.DATABASE_INTEGRATION_TEST) {
this.carDao.store(car);
}
});
Then("^a new car is added to the inventory$", (DataTable dataTable) -> {
Map<Long, Car> carRecordsById = this.carDao.getAllCars().stream().collect(Collectors.toMap(Car::getId, Function.identity()));
dataTable.asMaps().stream()
.map(record -> Long.parseLong(record.get("id")))
.forEach(carId -> {
assertTrue(String.format("car with id %s not found", carId), carRecordsById.containsKey(carId));
});
});
}
@Before(order = 100)
public void initdatabase() throws SQLException {
if (StepDefinitions.testMode == Mode.DATABASE_INTEGRATION_TEST) {
createTable();
Injector injector = Guice.createInjector(new CarModule());
carDao = injector.getInstance(CarDao.class);
}
}
private void createTable() throws SQLException {
try (Connection connection = createConnection()) {
Statement statement = connection.createStatement();
statement.execute("create table cars (" +
"id BIGINT not null primary key, " +
"brand varchar(100), " +
"model varchar(100), " +
"color varchar(20), " +
"year integer)");
}
}
@After
public void dropDatabase() throws SQLException {
if (StepDefinitions.testMode == Mode.DATABASE_INTEGRATION_TEST) {
try (Connection connection = createConnection()) {
Statement statement = connection.createStatement();
statement.execute("drop table cars");
}
}
}
private Connection createConnection() throws SQLException {
return new Database().getConnection();
}
}

View file

@ -0,0 +1,30 @@
package nl.sander.testautomation.tests.cars;
import nl.sander.testautomation.cars.Car;
import nl.sander.testautomation.cars.CarDao;
import nl.sander.testautomation.cars.CarService;
import nl.sander.testautomation.UnitTest;
import org.junit.Test;
import java.sql.SQLException;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
public class CarServiceTest implements UnitTest {
private final CarDao carDao = mock(CarDao.class);
private final CarService carService = classToTest(CarService.class);
@Test
public void testGetCar() throws SQLException {
when(carDao.getCar(10)).thenReturn(Car.builder().withColor("blue").build());
Optional<Car> car = carService.getCar(10);
assertTrue(car.isPresent());
assertEquals("blue", car.get().getColor());
}
}

View file

@ -0,0 +1,9 @@
Feature: car module
@DatabaseIntegrationTest
Scenario: add new car
When a user enters a car with id 1: a red volkswagen kever, built in 1967
Then a new car is added to the inventory
| id | brand | model | color | year |
| 1 | volkswagen | kever | red | 1967 |