Java >> Java tutorial >  >> Tag >> Spring

Integrationstest i Spring Boot Application

I dette indlæg vil jeg vise, hvordan vi kan tilføje integrationstest til en Spring Boot-applikation.

Integrationstests spiller en nøglerolle i at sikre kvaliteten af ​​applikationen. Med en ramme som Spring Boot er det endnu nemmere at integrere sådanne tests. Ikke desto mindre er det vigtigt at teste applikationer med integrationstest uden at implementere dem på applikationsserveren.

Integrationstest kan hjælpe med at teste dataadgangslaget i din applikation. Integrationstest hjælper også med at teste flere enheder. Til Spring Boot-applikationen skal vi køre en applikation i ApplicationContext for at kunne køre test. Integrationstest kan hjælpe med at teste undtagelseshåndtering.

Spring Boot Application

Til denne demo vil vi bygge en simpel Spring Boot-applikation med REST API'er. Vi vil bruge H2 In-Memory-databasen til at gemme dataene. Til sidst vil jeg vise, hvordan man skriver en integrationstest. Denne applikation læser en JSON-fil med sårbarheder fra National Vulnerability Database og gemmer den i H2-databasen. REST API'er giver en bruger mulighed for at hente disse data i et mere læsbart format.

Afhængigheder

Først vil vi bygge integrationstests i denne applikation, så vi bliver nødt til at inkludere afhængigheden spring-boot-starter-test .


dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'junit:junit:4.13.1'
	runtimeOnly 'com.h2database:h2:1.4.200'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Denne afhængighed af spring-boot-starter-test tillade os at tilføje testrelaterede annoteringer, som vi snart vil se.

REST API

Nu, som jeg sagde tidligere, vil vi have en REST API til at hente nationale sårbarhedsdatabasedata. Vi vil oprette en REST-controller med to API'er, én til at hente en liste over sårbarheder og én til at hente en sårbarhed med CVE-id.


@RestController
@RequestMapping("/v1/beacon23/vulnerabilities")
public class CveController
{

    @Autowired
    private CveService cveService;

    @GetMapping("/list")
    public List getAllCveItems(@RequestParam(required = false, name="fromDate") String fromDate, @RequestParam(required = false, name=
            "toDate") String toDate)
    {
        List cveDTOList = cveService.getCveItems(fromDate, toDate);

        if(cveDTOList == null || cveDTOList.isEmpty())
        {
            return new ArrayList<>();
        }
        else
        {
            return cveDTOList;
        }
    }

    @GetMapping
    public ResponseEntity getCveItemById(@RequestParam("cveId") String cveId)
    {
        CveDTO cveDTO = cveService.getCveItemByCveId(cveId);

        if(cveDTO != null)
        {
            return new ResponseEntity<>(cveDTO, HttpStatus.OK);
        }
        else
        {
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
    }

}

Så det har vi

  • /v1/beacon23/vulnerabilities/list – for at hente en liste over sårbarheder
  • /v1/beacon23/vulnerabilities?cveId=value – for at hente sårbarhed med CVE-id.

Service

Nu foregår det meste af forretningslogikken og valideringen i serviceklassen. Som vi så i vores API, bruger vi CVEService for at hente de nødvendige data.

    @Autowired
    public CveDataDao cveDataDao;

    public List getCveItems(String from, String to)
    {
        LOGGER.debug("The date range values are from = {} and to = {}", from, to);
        List cveDataList = cveDataDao.findAll();
        List cveDTOList = new ArrayList<>();

        for(CveData cveData : cveDataList)
        {
            List cveList = cveData.getCveItems();
            for(CveItem cveItem: cveList)
            {
                Date fromDate;
                Date toDate;

                if(!isNullOrEmpty(from) && !isNullOrEmpty(to))
                {
                    fromDate = DateUtil.formatDate(from);
                    toDate = DateUtil.formatDate(to);

                    Date publishedDate = DateUtil.formatDate(cveItem.getPublishedDate());

                    if(publishedDate.after(toDate) || publishedDate.before(fromDate))
                    {
                        continue;
                    }
                }
                CveDTO cveDTO = convertCveItemToCveDTO(cveItem);
                cveDTOList.add(cveDTO);
            }
        }
        return cveDTOList;
    }

    private boolean isNullOrEmpty (String str)
    {
        return (str == null || str.isEmpty());
    }

    private String buildDescription (List descriptionDataList)
    {
        if(descriptionDataList == null || descriptionDataList.isEmpty())
        {
            return EMPTY_STRING;
        }
        else
        {
            return descriptionDataList.get(0).getValue();
        }
    }

    private List buildReferenceUrls (List referenceDataList)
    {
        return referenceDataList.stream().map(it -> it.getUrl()).collect(Collectors.toList());
    }

    public CveDTO getCveItemByCveId(String cveId)
    {
        List cveDataList = cveDataDao.findAll();
        CveDTO cveDTO = null;

        for(CveData cveData : cveDataList)
        {
            List cveItems = cveData.getCveItems();

            Optional optionalCveItem =
                    cveItems.stream().filter(ci -> ci.getCve().getCveMetadata().getCveId().equals(cveId)).findAny();
            CveItem cveItem = null;
            if(optionalCveItem.isPresent())
            {
                cveItem = optionalCveItem.get();
            }
            else
            {
                return cveDTO;
            }
            cveDTO = convertCveItemToCveDTO(cveItem);
        }

        return cveDTO;
    }

Brug af @SpringBootTest

Spring Boot giver en annotation @SpringBootTest som vi kan bruge i integrationstest. Med denne annotation kan testene starte applikationskonteksten, der kan indeholde alle de objekter, vi har brug for, for at applikationen kan køre.

Integrationstests giver et næsten produktionslignende scenario til at teste vores kode. Testene er kommenteret med @SpringBootTest oprette den applikationskontekst, der bruges i vores tests gennem applikationsklassen, der er kommenteret med @SpringBootConfiguration .

Disse test starter en indlejret server, opretter et webmiljø og kører derefter @Test metoder til at lave integrationstest. Vi skal tilføje nogle få attributter for at sikre, at vi kan starte webmiljøet, mens vi bruger @SpringBootTest .

  • Attribut webEnvironment – At oprette et webmiljø med en standardport eller en tilfældig port.

Vi kan også videregive egenskaber til brug for test ved hjælp af en aktiv profil. Normalt bruger vi disse profiler til forskellige miljøer, men vi kan også bruge en speciel profil til kun test. Vi opretter application-dev.yml , application-prod.yml profiler. På samme måde kan vi oprette application-test.yml og brug annotationen @ActiveProfiles('test') i vores tests.

Eksempel på integrationstest

Til vores REST API vil vi oprette en integrationstest, der tester vores controller. Vi vil også bruge TestRestTemplate at hente data. Denne integrationstest vil se ud som nedenfor:


package com.betterjavacode.beacon23.tests;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertNotNull;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CveControllerTest
{
    @LocalServerPort
    private int port;

    TestRestTemplate testRestTemplate = new TestRestTemplate();

    HttpHeaders headers = new HttpHeaders();

    @Test
    public void testGetAllCveItems()
    {
        HttpEntity entity = new HttpEntity<>(null, headers);

        ResponseEntity responseEntity = testRestTemplate.exchange(createURLWithPort(
                "/v1/beacon23/vulnerabilities/list"),HttpMethod.GET, entity, String.class);

        assertNotNull(responseEntity);

    }


    private String createURLWithPort(String uri)
    {
        return "http://localhost:" + port + uri;
    }
}

Vi bruger @SpringBootTest annotation for vores testklasse og opsæt applikationskonteksten ved at bruge webEnvironment med en RANDOM_PORT. Vi håner også den lokale webserver ved at opsætte en mock-port med @LocalServerPort .

TestRestTemplate giver os mulighed for at simulere en klient, der vil kalde vores API. Når vi har kørt denne test (enten gennem gradle build ELLER gennem IntelliJ), vil vi se Spring Boot Application Context-opsætningen køre og applikationen køre på en tilfældig port.

En ulempe ved at oprette integrationstest med @SpringBootTest er, at det vil bremse opbygningen af ​​din applikation. I de fleste virksomhedsmiljøer vil du få dette sat op gennem kontinuerlig integration og kontinuerlig implementering. I sådanne scenarier sænker det processen med integration og implementering, hvis du har mange integrationstests.

Konklusion

Endelig skal du bruge integrationstest i Spring Boot-applikationen eller ej, det afhænger af din applikation. Men på trods af ulempen er det altid nyttigt at have integrationstest, der tillader test af flere enheder ad gangen. @SpringBootTest er en praktisk annotering, der kan bruges til at opsætte en applikationskontekst, hvilket giver os mulighed for at køre test tæt på et produktionsmiljø.

Referencer

  1. integrationstest med Spring Boot – Integrationstest

Java tag