mirror of
https://github.com/OtusTeam/Spring.git
synced 2026-05-30 10:50:42 +00:00
unit-testing-spring-boot
This commit is contained in:
@@ -2,4 +2,5 @@
|
||||
|
||||
## Описание проектов
|
||||
|
||||
* *unit-testing-plain-spring* - пример тестирования в проектах на чистом Spring
|
||||
* *unit-testing-plain-spring* - пример тестирования в проектах на чистом Spring
|
||||
* *unit-testing-spring-boot* - то же самое, только на Spring Boot
|
||||
@@ -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/
|
||||
@@ -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>
|
||||
+17
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+12
@@ -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);
|
||||
}
|
||||
+38
@@ -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()));
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package ru.otus.testingExample.dao;
|
||||
|
||||
public class InitialDataLoadingException extends RuntimeException {
|
||||
public InitialDataLoadingException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
+7
@@ -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";
|
||||
}
|
||||
+8
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package ru.otus.testingExample.services;
|
||||
|
||||
public interface GreetingService {
|
||||
void sayRussianGreeting();
|
||||
void sayEnglishGreeting();
|
||||
void sayChinaGreeting();
|
||||
}
|
||||
+32
@@ -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
|
||||
+42
@@ -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")
|
||||
);
|
||||
}
|
||||
}
|
||||
+15
@@ -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 {
|
||||
}
|
||||
+84
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+15
@@ -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
|
||||
Reference in New Issue
Block a user