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ć

statyczne mocki

final LoginContext loginContext = LoginContext.from(mockAuthentication);
        loginContextMockedStatic = Mockito.mockStatic(LoginContext.class);
        loginContextMockedStatic
                .when(() -> LoginContext.from(isNull()))
                .thenReturn(loginContext);

Tipsy

  • 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

junit5 + mockito

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

Hamcrest - dodaje trochę macherów

       <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)));

Groovy

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")
  }
}

testy mvc java

@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();
}

testy mvc groovy

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)
    }

Tools

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
⚠️ **GitHub.com Fallback** ⚠️