How to test main class of Spring-boot application

JavaSpring BootJunit

Java Problem Overview


I have a spring-boot application where my @SpringBootApplication starter class looks like a standard one. So I created many tests for all my functionalities and send the summary to [sonarqube][1] to see my coverage.

For my starter class Sonarqube tells me that I just have 60% coverage. So the average coverage is not good as expected.

[![enter image description here][2]][2]

My Test class is just the default one.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElectronicGiftcardServiceApplication.class)
public class ElectronicGiftcardServiceApplicationTests {

	@Test
	public void contextLoads() {
	}
}

So how can I test my main class in the starter class of my application? [1]: https://www.sonarqube.org/ [2]: https://i.stack.imgur.com/VCqsd.png

Java Solutions


Solution 1 - Java

All these answers seem overkill.
You don't add tests to make a metric tool happy.
Loading a Spring context of the application takes time. Don't add it in each developer build just to win about 0.1% of coverage in your application.
Here you don't cover only 1 statement from 1 public method. It represents nothing in terms of coverage in an application where thousands of statements are generally written.

First workaround : make your Spring Boot application class with no bean declared inside. If you have them, move them in a configuration class (for make them still cover by unit test). And then ignore your Spring Boot application class in the test coverage configuration.

Second workaround : if you really need to to cover the main() invocation (for organizational reasons for example), create a test for it but an integration test (executed by an continuous integration tool and not in each developer build) and document clearly the test class purpose :

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[] {});
   }
}

Solution 2 - Java

You can do something like this

@Test
public void applicationContextLoaded() {
}

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

Solution 3 - Java

I had the same goal (having a test that runs the main() method) and I noticed that simply adding a test method like @fg78nc said will in fact "start" the application twice : once by spring boot test framework, once via the explicit invocation of mainApp.main(new String[] {}), which I don't find elegant.

I ended up writing two test classes : one with @SpringBootTest annotation and the empty test method applicationContextLoaded(), another one without @SpringBootTest (only RunWith(SpringRunner.class)) that calls the main method.

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

ApplicationStartTest

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[] {});
  }
}

Overall, the application is still started two times, but because there is now two test classes. Of course, with only these two tests methods, it seems overkill, but usually more tests will be added to the class SpringBootApplicationTest taking advantage of @SpringBootTest setup.

Solution 4 - Java

I solved in a different way here. Since this method is there only as a bridge to Spring's run, I annotated the method with @lombok.Generated and now sonar ignores it when calculating the test coverage.

Other @Generated annotations, like javax.annotation.processing.Generated or javax.annotation.Generated might also work but I can't test now because my issue ticket was closed.

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

}

Solution 5 - Java

You can Mock SpringApplication since that is a dependency of the method under test. See how here. I.e.

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

}

Solution 6 - Java

In addition to the answers above, here is a unit test of a SpringBoot application's main method for if you are using JUnit 5 and Mockito 3.4+:

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

}	

It verifies that the static method run() on the SpringApplication class is called with the expected String array when we call ElectronicGiftCardServiceApplication.main().

Same idea as awgtek and Ramji Sridaran, but their solutions are for JUnit 4.

Solution 7 - Java

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

If you aim for 100% coverage, one thing you can do is simply not having a main method at all. You still require a class annotated with @SpringBootApplication but it can be empty.

Be warned though as it has its drawbacks and other tools that rely on main can break.

Solution 8 - Java

This simple mock test for SpringApplication does not invoke any methods but just tests the starter app. [uses 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"});
	}
}

Solution 9 - Java

Even though this question has been answered extensively I had a use case that is not covered here that is perhaps interesting to share. I am validating some properties at startup and I wanted to assert that the application would fail to start if these properties were configured wrong. In JUnit4 I could have done something like this:

@ActiveProfiles("incorrect")
@SpringBoot
public class NetworkProbeApplicationTest {

    @Test(expected=ConfigurationPropertiesBindException.class)
    public void contextShouldNotLoadWhenPropertiesIncorrect() {
    }
}

But in JUnit5 you can no longer add the "expected" value to your @Test annotation and you have to do it differently. And since I wanted to start the application with an incorrect set of properties I needed to pass in which profile to use as a main() argument. I could not really find this documented anywhere, but passing in arguments through the main() method requires you to prefix your arguments with a double hyphen and separate the key and value with an equals sign. A complete test would look like this:

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

Solution 10 - Java

Using junit

import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.boot.SpringApplication;

import static org.assertj.core.api.Assertions.*;
class WebsiteApplicationTests {
    
    @Test
    void testApplication() {
        MockedStatic<SpringApplication> utilities = Mockito.mockStatic(SpringApplication.class);
        utilities.when((MockedStatic.Verification) SpringApplication.run(WebsiteApplication.class, new String[]{})).thenReturn(null);
        WebsiteApplication.main(new String[]{});
        assertThat(SpringApplication.run(WebsiteApplication.class, new String[]{})).isEqualTo(null);
    }
}

Add these dependencies in pom.xml

<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-inline</artifactId>
			<version>${mockito.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-core</artifactId>
			<version>3.8.0</version>
			<scope>test</scope>
		</dependency>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPatrickView Question on Stackoverflow
Solution 1 - JavadavidxxxView Answer on Stackoverflow
Solution 2 - Javafg78ncView Answer on Stackoverflow
Solution 3 - Javamarcpa00View Answer on Stackoverflow
Solution 4 - JavaJosé Roberto Araújo JúniorView Answer on Stackoverflow
Solution 5 - JavaawgtekView Answer on Stackoverflow
Solution 6 - JavaValentijnView Answer on Stackoverflow
Solution 7 - JavaMariano LEANCEView Answer on Stackoverflow
Solution 8 - JavaRamjiView Answer on Stackoverflow
Solution 9 - JavaJonck van der KogelView Answer on Stackoverflow
Solution 10 - JavaSiddharth VaishView Answer on Stackoverflow