Hoe de hoofdklasse van de Spring-boot-toepassing te testen

Ik heb een spring-bootapplicatie waar mijn @SpringBootApplicationstartersklasse eruitziet als een standaard. Dus ik heb veel tests gemaakt voor al mijn functionaliteiten en de samenvatting naar sonarqubegestuurd om mijn dekking te zien.

Voor mijn startersles vertelt Sonarqube me dat ik maar 60% dekking heb. De gemiddelde dekking is dus niet goed zoals verwacht.

voer hier de afbeeldingsbeschrijving in

Mijn testklas is gewoon de standaardklas.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElectronicGiftcardServiceApplication.class)
public class ElectronicGiftcardServiceApplicationTests {
    @Test
    public void contextLoads() {
    }
}

Dus hoe kan ik mijn hoofdklasse testen in de startersklasse van mijn applicatie?


Antwoord 1, autoriteit 100%

Al deze antwoorden lijken overdreven.
Je voegt geen tests toe om een ​​metrische tool blij te maken.
Het laden van een Spring-context van de applicatie kost tijd. Voeg het niet toe aan elke build voor ontwikkelaars om ongeveer 0,1% dekking in uw applicatie te winnen.
Hier behandel je niet slechts 1 verklaringvan 1 openbare methode. Het stelt niets voor in termen van dekking in een toepassing waar duizenden verklaringen over het algemeen worden geschreven.

Eerste oplossing: maak je Spring Boot-toepassingsklasse zonder dat er een bean in wordt gedeclareerd. Als je ze hebt, verplaats ze dan naar een configuratieklasse (om ze nog steeds te laten dekken door eenheidstest). En negeer vervolgens uw Spring Boot-toepassingsklasse in de test dekking configuratie.

Tweede tijdelijke oplossing: als het echt nodig is om de main()-aanroep te dekken (bijvoorbeeld om organisatorische redenen), maak er dan een test voor maar een integratietest (uitgevoerd door een continue integratietool en niet in elke build voor ontwikkelaars) en documenteer duidelijk het doel van de testklasse:

import org.junit.Test;
// Test class added ONLY to cover main() invocation not covered by application tests.
public class MyApplicationIT {
   @Test
   public void main() {
      MyApplication.main(new String[] {});
   }
}

Antwoord 2, autoriteit 19%

Je kunt zoiets als dit doen

@Test
public void applicationContextLoaded() {
}
@Test
public void applicationContextTest() {
    mainApp.main(new String[] {});
}

Antwoord 3, autoriteit 10%

Ik had hetzelfde doel (een test hebben die de methode main() uitvoert) en ik merkte dat het toevoegen van een testmethode zoals @fg78nc zei, de toepassing in feite twee keer “start”: eenmaal door spring boot test framework, eenmaal via de expliciete aanroep van mainApp.main(new String[] {}), wat ik niet elegant vind.

Ik heb uiteindelijk twee testklassen geschreven: een met de annotatie @SpringBootTesten de lege testmethode applicationContextLoaded(), een andere zonder @SpringBootTest(alleen RunWith(SpringRunner.class)) die de hoofdmethode aanroept.

SpringBootApplicationTest

package example;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.boot.test.context.SpringBootTest;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootApplicationTest {
  @Test
  public void contextLoads() {
  }
}

ApplicatieStartTest

package example;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
public class ApplicationStartTest {
  @Test
  public void applicationStarts() {
    ExampleApplication.main(new String[] {});
  }
}

Over het algemeen wordt de applicatie nog steeds twee keer gestart, maar omdat er nu twee testklassen zijn. Natuurlijk lijkt het met alleen deze twee testmethoden overdreven, maar meestal zullen er meer tests worden toegevoegd aan de klasse SpringBootApplicationTest, waarbij gebruik wordt gemaakt van de @SpringBootTest-configuratie.


Antwoord 4, autoriteit 5%

Ik heb het hier op een andere manier opgelost. Aangezien deze methode er alleen is als een brug naar Spring’s run, heb ik de methode geannoteerd met @lombok.Generateden nu negeert sonar het bij het berekenen van de testdekking.

Andere @Generatedannotaties, zoals javax.annotation.processing.Generatedof javax.annotation.Generatedwerken misschien ook, maar ik kan’ t test nu omdat mijn uitgifteticket is gesloten.

package com.stackoverflow;
import lombok.Generated;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
    @Generated
    public static void main(String... args) {
        SpringApplication.run(Application.class, args);
    }
}

Antwoord 5, autoriteit 3%

Je kunt SpringApplicationbespotten, aangezien dat afhankelijk is van de methode die wordt getest. Bekijk hoe hier.
D.w.z.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.boot.SpringApplication;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
@RunWith(PowerMockRunner.class)
public class ElectronicGiftcardServiceApplicationTest {
    @Test
    @PrepareForTest(SpringApplication.class)
    public void main() {
        mockStatic(SpringApplication.class);
        ElectronicGiftcardServiceApplication.main(new String[]{"Hello", "World"});
        verifyStatic(SpringApplication.class);
        SpringApplication.run(ElectronicGiftcardServiceApplication.class, new String[]{"Hello", "World"});
    }
}

Antwoord 6, autoriteit 3%

In aanvulling op de bovenstaande antwoorden, is hier een eenheidstest van de hoofdmethode van een SpringBoot-toepassing voor als u JUnit 5 en Mockito 3.4+ gebruikt:

try (MockedStatic<SpringApplication> mocked = mockStatic(SpringApplication.class)) {
   mocked.when(() -> { SpringApplication.run(ElectronicGiftCardServiceApplication.class, 
      new String[] { "foo", "bar" }); })
         .thenReturn(Mockito.mock(ConfigurableApplicationContext.class));
   ElectronicGiftCardServiceApplication.main(new String[] { "foo", "bar" });
   mocked.verify(() -> { SpringApplication.run(ElectronicGiftCardServiceApplication.class, 
      new String[] { "foo", "bar" }); });
}   

Het controleert of de statische methode run() op de SpringApplication-klasse wordt aangeroepen met de verwachte String-array wanneer we ElectronicGiftCardServiceApplication.main() aanroepen.

Hetzelfde idee als awgtek en Ramji Sridaran, maar hun oplossingen zijn voor 4 juni.


Antwoord 7, autoriteit 2%

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <mainClass>your.awesome.package.Application</mainClass> 
    </configuration>
</plugin>

Als u streeft naar 100% dekking, kunt u eenvoudigweg helemaal geen hoofdmethode hebben. Je hebt nog steeds een klas nodig die is geannoteerd met @SpringBootApplication, maar deze kan leeg zijn.

Wees echter gewaarschuwd, want het heeft zijn nadelen en andere tools die afhankelijk zijn van mainkunnen kapot gaan.


Antwoord 8

Deze eenvoudige schijntest voor SpringApplication roept geen methoden aan, maar test alleen de starter-app. [gebruikt PowerMockRunner.class]

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.boot.SpringApplication;
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "javax.management.*"})
public class JobsAppStarterTest {
    @Test
    @PrepareForTest(SpringApplication.class)
    public void testSpringStartUp() {
        PowerMockito.mockStatic(SpringApplication.class);
        SpringApplication.run(JobsAppStarter.class, new String[] {"args"});
        JobsAppStarter.main(new String[] {"args"});
    }
}

Antwoord 9

Hoewel deze vraag uitgebreid is beantwoord, had ik een use-case die hier niet wordt behandeld en die misschien interessant is om te delen. Ik valideer enkele eigenschappen bij het opstarten en ik wilde beweren dat de toepassing niet zou starten als deze eigenschappen verkeerd waren geconfigureerd. In JUnit4 had ik zoiets als dit kunnen doen:

@ActiveProfiles("incorrect")
@SpringBoot
public class NetworkProbeApplicationTest {
    @Test(expected=ConfigurationPropertiesBindException.class)
    public void contextShouldNotLoadWhenPropertiesIncorrect() {
    }
}

Maar in JUnit5 kun je de “verwachte” waarde niet langer toevoegen aan je @Test-annotatie en moet je het anders doen. En aangezien ik de toepassing met een onjuiste set eigenschappen wilde starten, moest ik doorgeven welk profiel als hoofdargument () moest worden gebruikt. Ik kon dit nergens echt gedocumenteerd vinden, maar het invoeren van argumenten via de main()-methode vereist dat je je argumenten voorvoegt met een dubbel koppelteken en de sleutel en waarde scheidt met een gelijkteken. Een volledige test ziet er als volgt uit:

import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindException;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class NetworkProbeApplicationTest {
    @Test
    public void contextShouldNotLoadWhenPropertiesIncorrect() {
        Exception exception = assertThrows(ConfigurationPropertiesBindException.class, () -> {
            SpringApplication.run(NetworkProbeApplication.class, "--spring.profiles.active=incorrect");
        });
        String expectedMessage = "Error creating bean with name 'dnsConfiguration': Could not bind properties to 'DnsConfiguration' : prefix=dns";
        assertTrue(exception.getMessage().contains(expectedMessage));
    }
}

Other episodes