unit-testing-spring-boot

This commit is contained in:
ydvorzhetskiy
2019-03-23 00:27:03 +03:00
parent 0d218023a3
commit 05d08b1f3e
23 changed files with 442 additions and 1 deletions
+2 -1
View File
@@ -2,4 +2,5 @@
## Описание проектов
* *unit-testing-plain-spring* - пример тестирования в проектах на чистом Spring
* *unit-testing-plain-spring* - пример тестирования в проектах на чистом Spring
* *unit-testing-spring-boot* - то же самое, только на Spring Boot
+25
View File
@@ -0,0 +1,25 @@
/target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
/build/
+84
View File
@@ -0,0 +1,84 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ru.otus</groupId>
<artifactId>unit-testing-spring-boot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>unit-testing-spring-boot</name>
<description>Unit testing example</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Для работы с Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Для тестирования Spring Boot, тут и mockito и AssertJ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- Убираем старый JUnit 4, мы тестируем с JUnit 5 -->
<exclusions>
<exclusion>
<groupId>org.junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Тестирование-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,17 @@
package ru.otus.testingExample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import ru.otus.testingExample.services.GreetingService;
@SpringBootApplication
public class TestingExampleSpringBootApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(TestingExampleSpringBootApplication.class, args);
GreetingService greetingService = context.getBean(GreetingService.class);
greetingService.sayChinaGreeting();
}
}
@@ -0,0 +1,16 @@
package ru.otus.testingExample.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.otus.testingExample.dao.GreetingDao;
import ru.otus.testingExample.dao.GreetingDaoImpl;
@Configuration
public class ApplicationConfig {
@Bean
GreetingDao greetingDao(ApplicationProperties properties) {
return new GreetingDaoImpl(properties.getInitialData());
}
}
@@ -0,0 +1,12 @@
package ru.otus.testingExample.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties
public class ApplicationProperties {
private String initialData;
}
@@ -0,0 +1,7 @@
package ru.otus.testingExample.dao;
import java.util.Optional;
public interface GreetingDao {
Optional<String> findGreetingByCountryCode(String countryCode);
}
@@ -0,0 +1,38 @@
package ru.otus.testingExample.dao;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
public class GreetingDaoImpl implements GreetingDao {
private final String initialDataPropertiesResourceName;
private final Map<String, String> greetingsByCountryCode;
public GreetingDaoImpl(String initialDataPropertiesResourceName) {
this.initialDataPropertiesResourceName = initialDataPropertiesResourceName;
greetingsByCountryCode = new HashMap<>();
loadInitialData();
}
private void loadInitialData() {
greetingsByCountryCode.clear();
try {
try (InputStream initialDataPropertiesResourceStream = getClass().getClassLoader().getResourceAsStream(initialDataPropertiesResourceName)) {
Properties properties = new Properties();
properties.load(new InputStreamReader(Objects.requireNonNull(initialDataPropertiesResourceStream), "UTF-8"));
properties.forEach((key, value) -> greetingsByCountryCode.putIfAbsent(((String) key).toUpperCase(), (String) value));
}
}catch (Exception e) {
throw new InitialDataLoadingException(e);
}
}
public Optional<String> findGreetingByCountryCode(String countryCode) {
return Optional.ofNullable(greetingsByCountryCode.get(countryCode.toUpperCase()));
}
}
@@ -0,0 +1,7 @@
package ru.otus.testingExample.dao;
public class InitialDataLoadingException extends RuntimeException {
public InitialDataLoadingException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,11 @@
package ru.otus.testingExample.services;
import org.springframework.stereotype.Service;
@Service
public class ConsoleIOService implements IOService {
@Override
public void out(String message) {
System.out.println(message);
}
}
@@ -0,0 +1,7 @@
package ru.otus.testingExample.services;
public class CountryCodes {
public static final String COUNTRY_CODE_RU = "RU";
public static final String COUNTRY_CODE_EN = "EN";
public static final String COUNTRY_CODE_CN = "CN";
}
@@ -0,0 +1,8 @@
package ru.otus.testingExample.services;
public class GreetingNotFoundException extends RuntimeException {
public GreetingNotFoundException(String countryCode) {
super(String.format("No greeting was found for country code %s", countryCode));
}
}
@@ -0,0 +1,7 @@
package ru.otus.testingExample.services;
public interface GreetingService {
void sayRussianGreeting();
void sayEnglishGreeting();
void sayChinaGreeting();
}
@@ -0,0 +1,32 @@
package ru.otus.testingExample.services;
import org.springframework.stereotype.Service;
import ru.otus.testingExample.dao.GreetingDao;
import static ru.otus.testingExample.services.CountryCodes.*;
@Service
public class GreetingServiceImpl implements GreetingService {
private final IOService ioService;
private final GreetingDao greetingDao;
public GreetingServiceImpl(IOService ioService, GreetingDao greetingDao) {
this.ioService = ioService;
this.greetingDao = greetingDao;
}
@Override
public void sayRussianGreeting() {
ioService.out(greetingDao.findGreetingByCountryCode(COUNTRY_CODE_RU).orElseThrow(() -> new GreetingNotFoundException(COUNTRY_CODE_RU)));
}
@Override
public void sayEnglishGreeting() {
ioService.out(greetingDao.findGreetingByCountryCode(COUNTRY_CODE_EN).orElseThrow(() -> new GreetingNotFoundException(COUNTRY_CODE_EN)));
}
@Override
public void sayChinaGreeting() {
ioService.out(greetingDao.findGreetingByCountryCode(COUNTRY_CODE_CN).orElseThrow(() -> new GreetingNotFoundException(COUNTRY_CODE_CN)));
}
}
@@ -0,0 +1,5 @@
package ru.otus.testingExample.services;
public interface IOService {
void out(String message);
}
@@ -0,0 +1 @@
initialData: "initial_data/greetings.properties"
@@ -0,0 +1,3 @@
RU=Привет
EN=Hello
CN=Nihao
@@ -0,0 +1,42 @@
package ru.otus.testingExample.springboot.dao;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import ru.otus.testingExample.dao.GreetingDao;
import java.util.Optional;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
// Тест с поднятием контекста spring boot
@DisplayName("Дао для работы с приветствиями ")
@SpringBootTest
class GreetingDaoImplTest {
@Autowired
private GreetingDao greetingDao;
@ParameterizedTest
@MethodSource("generateData")
@DisplayName("дожен возвращать не пустое, корректное приветствие для кода указанной страны в любом регистре")
void shouldReturnNotEmptyGreetingByCaseInsensitiveCountryCode(String countryCode, String expectedGreeting) {
Optional<String> greetingOptional = greetingDao.findGreetingByCountryCode(countryCode);
assertThat(greetingOptional).isNotEmpty().hasValue(expectedGreeting);
}
private static Stream<Arguments> generateData() {
return Stream.of(
Arguments.of("ru", "Тестовый привет"),
Arguments.of("RU", "Тестовый привет"),
Arguments.of("en", "Тестовый hello"),
Arguments.of("EN", "Тестовый hello"),
Arguments.of("cn", "Тестовый nihao"),
Arguments.of("CN", "Тестовый nihao")
);
}
}
@@ -0,0 +1,15 @@
package ru.otus.testingExample.springboot.dao;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
/*
Класс ограничивающий контекст. В тесты, лежащие в одном пакете с данной конфигурацией попадут только те бины, которые лежать в пакетах, указанных в ComponentScan
Подробнее см. https://otus.ru/nest/post/429/
*/
@SpringBootConfiguration
@EnableConfigurationProperties
@ComponentScan({"ru.otus.testingExample.config", "ru.otus.testingExample.dao"})
public class TestContextConfig {
}
@@ -0,0 +1,84 @@
package ru.otus.testingExample.springboot.services;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import ru.otus.testingExample.dao.GreetingDao;
import ru.otus.testingExample.services.GreetingNotFoundException;
import ru.otus.testingExample.services.GreetingService;
import ru.otus.testingExample.services.GreetingServiceImpl;
import ru.otus.testingExample.services.IOService;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static ru.otus.testingExample.services.CountryCodes.*;
// Тест с поднятием контекста spring boot
@DisplayName("Методы сервиса приветствий должны ")
@SpringBootTest(classes = GreetingServiceImpl.class)
class GreetingServiceImplTest {
@MockBean
private IOService ioService;
@MockBean
private GreetingDao greetingDao;
@Autowired
private GreetingService greetingService;
@BeforeEach
void setUp() {
given(greetingDao.findGreetingByCountryCode(any())).willReturn(Optional.of(""));
}
@Test
@DisplayName("вызывать методы ioService и greetingDao с нужными параметрами. Текущий метод: sayRussianGreeting")
void shouldExecuteServiceMethodsForRussianGreeting() {
greetingService.sayRussianGreeting();
verify(ioService, times(1)).out("");
verify(greetingDao, times(1)).findGreetingByCountryCode(COUNTRY_CODE_RU);
}
@Test
@DisplayName("вызывать методы ioService и greetingDao с нужными параметрами. Текущий метод: sayEnglishGreeting")
void shouldExecuteServiceMethodsForEnglishGreeting() {
greetingService.sayEnglishGreeting();
verify(ioService, times(1)).out("");
verify(greetingDao, times(1)).findGreetingByCountryCode(COUNTRY_CODE_EN);
}
@Test
@DisplayName("вызывать методы ioService и greetingDao с нужными параметрами. Текущий метод: sayChinaGreeting")
void shouldExecuteServiceMethodsForChinaGreeting() {
greetingService.sayChinaGreeting();
verify(ioService, times(1)).out("");
verify(greetingDao, times(1)).findGreetingByCountryCode(COUNTRY_CODE_CN);
}
@Test
@DisplayName(" не бросать исключение если приветствие найдено")
void shouldNotThrowExceptionIfGreetingExists() {
assertThatCode(() -> greetingService.sayChinaGreeting()).doesNotThrowAnyException();
}
@Test
@DisplayName(" бросать исключение если приветствие не найдено")
void shouldThrowFoundExceptionIfGreetingNotExists() {
given(greetingDao.findGreetingByCountryCode(any())).willReturn(Optional.empty());
assertThatThrownBy(() -> greetingService.sayChinaGreeting()).isInstanceOf(GreetingNotFoundException.class);
}
}
@@ -0,0 +1,15 @@
package ru.otus.testingExample.springboot.services;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
/*
Класс ограничивающий контекст. В тесты, лежащие в одном пакете с данной конфигурацией попадут только те бины, которые лежать в пакетах, указанных в ComponentScan
Подробнее см. https://otus.ru/nest/post/429/
*/
@SpringBootConfiguration
@EnableConfigurationProperties
@ComponentScan({"ru.otus.testingExample.config", "ru.otus.testingExample.dao", "ru.otus.testingExample.services"})
public class TestContextConfig {
}
@@ -0,0 +1 @@
initialData: "initial_data/greetings.properties"
@@ -0,0 +1,3 @@
RU=Тестовый привет
EN=Тестовый hello
CN=Тестовый nihao