Tests & Tools - rlip/java GitHub Wiki
ArgThat i lamda lub argumentCaptor
Mock to stub na sterydach ;).
Stub to obiekt, który podstawiasz w teście za właściwą implementację. Stub pozwala jedynie na określenie zachowania obiektu, który imituje.
Mock to także obiekt, który podstawiasz w teście za właściwą implementację. W tym przypadku, poza określeniem zachowania3 masz możliwość jego weryfikacji. Innymi słowy mock’i pozwalają sprawdzać czy dany obiekt został użyty, jakie metody były wywołane w trakcie testu, jakie parametry były użyte w trakcie tych wywołań.
stub - symulacja jakiegoś zachowania, stosujemy jeśli jedyną rzeczą które chcemy zrobić z daną klasą to zasymulować zwracanie czegoś dla jakieś funkcji
mock - to co stub, ale ważne są interakcje, ale możemy tu spawdzić czy dany obiek został użyty, jaka metoda została wykonana ile razy i może w prawidłowej kolejności
spy - wszystko działa jak w oryginale, możemy coś nadpisać
final LoginContext loginContext = LoginContext.from(mockAuthentication);
loginContextMockedStatic = Mockito.mockStatic(LoginContext.class);
loginContextMockedStatic
.when(() -> LoginContext.from(isNull()))
.thenReturn(loginContext);
- testowanie czasu - timeTranformer.setTime(TransofmingTime.INSTANCE)
- zaczekanie na coś - github.com/awaitility/awaitility
- testy sanpshotowe - biblioteka approvals - approvaltests.com
- snapshotowe z wyk. orbazków - ligthweight java visualizer - atp-mitp/ljv
- https://github.com/diffplug/spotless - plugin do zmiany, np. marginesów, kolejności importu
- errorprone.info - coś mi nie działa
Zalety:
@DisplayName("Naza testu")
assertAll(
() -> assertEquals( ... ),
() -> assertEquals( ... ),
);
assertThrows(Błąd.class, metodaZBłędem)
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import org.mockito.junit.jupiter.MockitoExtension;
//@SpringBootTest -- włączyć jeśli kontekst
@ExtendWith(MockitoExtension.class) // @Mock włączamy albo tak, albo przez MockitoAnnotations::initMocks, a bez tego można i tak użyć Mockito.mock
public class TodoBusinessImpMockInjectTest {
@Mock
private TodoService todoServiceMock;
@InjectMocks
TodoBusinessImpl todoBusinessImpl;
@Captor
ArgumentCaptor<String> stringArgumentCaptor;
@Test
public void testRetrieveTodosRelatedToSpring_usingAMock() {
// TodoService todoServiceMock = Mockito.mock(TodoService.class);
List<String> todos = Arrays.asList("Learn Spring MCV", "Learn Spring", "Learn Dane");
when(todoServiceMock.retrieveTodos("Dummy")).thenReturn(todos);
// TodoBusinessImpl todoBusinessImpl = new TodoBusinessImpl(todoServiceMock);
List<String> filteredTodos = todoBusinessImpl.retrieveTodosRelatedToSpring("Dummy");
assertEquals(2, filteredTodos.size());
assertEquals("Learn Spring MCV", filteredTodos.get(0));
assertEquals("Learn Spring","Learn Spring");
}
------------
<properties>
<java.version>1.8</java.version>
<junit-jupiter.version>5.5.2</junit-jupiter.version>
<mockito.version>2.24.0</mockito.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
</parent>
<dependencies>
<!-- mvc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- exclude junit 4 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- junit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
</plugin>
@SpringBootTest - przed klasą uruchamia kontekst springowy
@Test - metoda jest testem
@RepetedTest(100) - wykonuje się 100 razy
@ParameterizedTest - zparametryzowany
w junit4:
@Mock - przed parametrem robi taką sztuczna klasę i przed klasą @RunWith(MockitoJUnitRunner.class)
@Disabled("Not implemented yet")
@DisplayName("1 + 1 = 2") - zmieniana nazwa
@BeforeAll
public static void init(){
//Computation expensive steps
ElectricityMeterTest.electricityMeter = new ElectricityMeter();
}
@BeforeEach
public void setUp(){
//assure deterministic start environment
electricityMeter.reset();
}
@AfterAll
public void release(){
//release connections, files, etc.
}
@AfterEach
public void tearDown(){
electricityMeter.reset();
}
@Test
void addKwh_newMeter_properAction() {
ElectricityMeter electricityMeter = ElectricityMeterTest.electricityMeter;
electricityMeter.addKwh(1);
Assertions.assertEquals(1, electricityMeter.getKwh());
}
----------------------------------------
@DisplayName("Should not throw exceptions When saveConsentList")
@Test
public void shouldNotThrowExceptionsWhenSaveConsentList() {
doNothing()
.when(restTemplate)
.put(
isA(URI.class),
isA(Object.class)
);
//When, Then
assertDoesNotThrow(() -> {
List<SaveCustomerConsentRequest> saveCustomerConsentRequestList = new ArrayList<>();
saveCustomerConsentRequestList.add(new SaveCustomerConsentRequest());
customerConsentClient.saveConsentList(saveCustomerConsentRequestList);
});
}
assertThatThrownBy(() -> new DictionaryEntrySystemMaskFilter(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Maska systemu nie może być pusta");
----------------------------------------
//mozna grupować testy
@RunWith(JUnitPlatform.class)
//@SelectPackages("electricity") // tak można cały pakiet
@SelectClasses({
ElectricityMeterTest.class,
TwoElectricityMeterTest.class,
})
public class ElectricityMeterSuite {
}
----------------------------------------
//Mocki:
testCompile "org.mockito:mockito-core:2.+"
electricityMeter = new ElectricityMeter();
// tak mock zastąpia wszystkie klasy pustym blokiem kodu i nie możemy wykonać żadnych funkcji:
electricityMeter = Mockito.mock(ElectricityMeter.class);
// tak można wykożystać istniejący obiekt i tylko nadpisać metody, które ustawimy, ale to nie jest zalecane!
electricityMeter = Mockito.spy(electricityMeter);
//tak zaślepiamy:
Mockito.when(electricityMeter.isTariffNow()).thenReturn(true);
----------------------------------------
//testy parametryzowane
@RunWith(Parameterized.class) - przed klasą
// trzeba dodać te wszystkie parametry jako zmienne, konstruktor je ustawiające i metodę która zwraca dane:
@Parameterized.Parameters
public static Collection inputData() {
return Arrays.asList(new Object[][]{{100, false, 100, 0}, ...
//a można też tak
@ParameterizedTest(name = "{0} + {1} = {2}") //inna nazwa
@CsvSource({
"0, 1, 1",
"1, 2, 3",
"49, 51, 100",
"1, 100, 101"
})
void add(int first, int second, int expectedResult) {
//lub
@ParameterizedTest
@MethodSource("inputData")
void GivenDependOnTariffWhenKwhAdditionThenTariffCounterIsIncreased(Integer kwhUsedInCase,
boolean isTariffInCase,
float sumKwhInCase,
float sumKwhTariffInCase) {
--------------------------------
przechwytywanie błędów
void getHowMoreExpensiveNormalIs() {
ArithmeticException arithmeticException = Assertions.assertThrows(ArithmeticException.class, () -> {
ElectricityMeter electricityMeter = ExistElectricityMeterTest.electricityMeter;
electricityMeter.setCentsForKwh(90);
electricityMeter.getHowMoreExpensiveNormalIs();
});
Assertions.assertEquals(arithmeticException.getMessage(), "/ by zero");
}
--------------------------------
wielokrotne - mamy kolekcję asercji do sprawdzenia
Assertions.assertAll("Testing Tariff",
()-> assertEquals(electricityMeter.getKwhTariff(), 0),
()-> assertTrue(electricityMeter.getKwh()%50==0));
--------------------------------
dependencies {
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.2'
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.4.2'
testCompile group: 'org.junit.platform', name: 'junit-platform-suite-api', version: '1.3.2'
testCompile group: 'org.junit.platform', name: 'junit-platform-runner', version: '1.4.0'
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.4.2")
testCompile("org.junit.jupiter:junit-jupiter-api:5.4.2")
testCompile "org.mockito:mockito-core:2.+"
}
----------------------------------------------------------------
//Given - setup
TodoService todoServiceMock = mock(TodoService.class);
List<String> todos = Arrays.asList("Learn Spring MCV", "Learn Spring", "Learn Dane");
// when(todoServiceMock.retrieveTodos("Dummy")).thenReturn(todos); // to samo co niżej!
given(todoServiceMock.retrieveTodos("Dummy")).willReturn(todos);
TodoBusinessImpl todoBusiness = new TodoBusinessImpl(todoServiceMock);
//When - actual method
List<String> filteredTodos = todoBusiness.retrieveTodosRelatedToSpring("Dummy");
//Then - co się stało
assertEquals(filteredTodos.size(), 2); //to co niżej
assertThat(filteredTodos.size(), is(2));
// metoda z takim argumentem została wykonana dowolną liczbę razy
verify(todoServiceMock).deleteTodo("Learn to Dane");
//to samo co:
then(todoServiceMock).should().deleteTodo("Learn to Dane");
// metoda z takim argumentem została wykonana raz
verify(todoServiceMock, times(1)).deleteTodo("Learn to Dane");
// można też dać never(), atLeast()
verify(todoServiceMock, never()).deleteTodo("Learn Spring"); //to samo co:
then(todoServiceMock).should(never()).deleteTodo("Learn Spring");
-----------------------
<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>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
Powermock - rozszerzenie mokito, np. dodaje testowanie statycznych klas
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
---------
import static org.hamcrest.Matchers.*; // tu można zobaczyć jakie są
assertThat(scores, hasSize(4));
assertThat(scores, hasItems(100, 101));
assertThat(scores, everyItem(greaterThan(90)));
assertThat(scores, everyItem(lessThan(200)));
when // zawsze ma być z then - tu wywołanie funkcji
then // tu porównania
expect // tu porównanie, po tym można dać where
where // tu wypelnienie wartosciami
class CurrencyRateServiceUnitTestGroovy extends Specification {
CurrencyRateServiceImpl currencyRateService = new CurrencyRateServiceImpl()
@Unroll // z tym robi osobne testy i wstawia zmienne do nazwy, można też dodać nad klasą
//te zmienne w nawiasie poniżej nie sa obowiązkowe
def "Given #ccode When getCurrencySuffixForCcode Then #expectedSuffix"(String ccode, String expectedSuffix) {
expect:
expectedSuffix == currencyRateService.getCurrencySuffixForCcode(ccode)
where:
//można tak:
// [ccode, expectedSuffix] << [['0',''], ['8', '7']]
//albo tak:
// ccode << ['0', '8']
// expectedSuffix << ['', '7']
//albo tak:
ccode || expectedSuffix
"0" || ""
"8" || "7"
}
//////////////////////////////////////////////////////////
class DatabaseDrivenSpec extends Specification {
//z @shared nie będzie to reinicjalizowane pomiędzy testami
@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver") //
// setupSpec - wykonana raz przed testami
def setupSpec() {
sql.execute'''
create table maxdata (
id int primary key,
a int,
b int,
c int)
'''
sql.execute("insert into maxdata values (1, 3, 7, 7), (2, 5, 4, 5), (3, 9, 9, 9)")
}
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
[a, b, c] << sql.rows("select a, b, c from maxdata")
}
}
//////////////////////////////////////////////////////////
@Unroll
def "#person.name is a #sex.toLowerCase() person"() {
expect:
person.sex == sex
where:
person || sex
new Person(name: "Fred") || "Male"
new Person(name: "Wilma") || "Female"
}
static class Person {
String name
String getSex() {
name == "Fred" ? "Male" : "Female"
}
}
Mocki:
class Publisher {
def subscribers = []
def send(event) {
subscribers.each {
try {
it.receive(event)
} catch (Exception e) {}
}
}
}
interface Subscriber {
def receive(event)
}
class PublisherSpec extends Specification {
def pub = new Publisher()
def sub1 = Mock(Subscriber)
def sub2 = Mock(Subscriber)
def setup() {
pub.subscribers << sub1 << sub2
}
def "delivers events to all subscribers"() {
when:
pub.send("event")
then:
1 * sub1.receive("event") //wykonanie 1 raz
then: //niepotrzebne, ale tak to zachowuje kolejność żę sub2 jest po sub1
1 * sub2.receive("event")
}
def "can cope with misbehaving subscribers"() {
// podkreślenie to 1 pojeddyńczy argument dowolnego typu
sub1.receive(_) >> { throw new Exception() }
when:
pub.send("event1")
pub.send("event2")
then:
1 * sub2.receive("event1") //wykonanie 1 raz
1 * sub2.receive("event2")
}
}
@ExtendWith(MockitoExtension.class)
class RegonDataControllerTest {
private MockMvc mockMvc;
@Mock
private RegonDataService regonDataService;
@InjectMocks
private RegonDataController controller;
private static final String NIP = "1234";
private static final String PROCESS_NUMBER = "ABC123";
private static final int DATA_VALIDITY_DAYS = 15;
@BeforeEach
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@DisplayName("Should status ok when patch findRegonDataByNip")
@Test
void shouldStatusOkWhenPatchFindRegonDataByNip() throws Exception {
mockMvc.perform(
patch(REGON_DATA + REGISTERED_PROCESSES + "/" + PROCESS_NUMBER)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"nip\":\"" + NIP + "\",\"dataValidityDays\":" + DATA_VALIDITY_DAYS + "}")
)
.andExpect(status().isOk());
}
https://www.baeldung.com/integration-testing-in-spring
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { ApplicationConfig.class })
@WebAppConfiguration
public class GreetControllerIntegrationTest {
@Autowired
private WebApplicationContext webApplicationContext;
@BeforeEach
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
}
class RegonDataControllerTest extends Specification {
public static final String NIP = "1234"
public static final String PROCESS_NUMBER = "ABC123"
public static final int DATA_VALIDITY_DAYS = 15
private static Gson gson = new Gson()
def regonDataService
def objectParamsController
private MockMvc mockMvc
void setup() {
regonDataService = Mock(RegonDataService)
regonDataService.findRegonDataByNip(_ as String, _ as String, _ as int) >> new RegonDataResponse()
objectParamsController = new RegonDataController(regonDataService)
mockMvc = MockMvcBuilders.standaloneSetup(objectParamsController).build()
}
def "Should status ok When patch findRegonDataByNip"() {
when:
Map params = [
nip : NIP,
dataValidityDays: DATA_VALIDITY_DAYS
]
def results = mockMvc.perform(
patch(REGON_DATA + REGISTERED_PROCESSES + "/" + PROCESS_NUMBER)
.contentType(MediaType.APPLICATION_JSON)
.content(gson.toJson(params))
)
then:
results
.andExpect(status().isOk())
1 * regonDataService.findRegonDataByNip(NIP, PROCESS_NUMBER, DATA_VALIDITY_DAYS)
}
mvn versions:display-property-updates
mvn versions:display-plugin-updates
mvn versions:display-dependency-updates
mvn com.redhat.victims.maven:security-versions:check
mvn org.owasp:dependency-check-maven:check
- jfairy - biblioteka do ściągania fakowych danych personalnych
- sdkman.io - różne wersje javy
- asdf
- https://editorconfig.org/
- https://github.com/cdimascio/dotenv-java