Search code examples
javaspring-bootjunitintegration-testingjunit4

Skipping integration tests in spring boot on VPN DNS availability


I have a moderately heavy springboot service, it takes 10-15 seconds to boot on a happy flow, and (1-2) minutes to fail on a retry/failover flow. This is ok for my business flows, and is how I expect a healthy service to behave.

I have integration tests (that run some end-to-end flows in my service), that can only test the actual integration status while the test machine (or dev machine) is connected to a specific VPN.

I want to auto skip integration tests if I'm not connected to VPN.

consider the following code

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Server.class}, // auto scans a bunch of components
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // slow loading context
public class IntegrationTest {
    @BeforeClass
    public static void beforeClass() {
        Assume.assumeTrue(DnsTool.vpnConnected()); // fast failing test
    }

    @Test
    public void testIntegration() {
        // some test logic
    }
}

When the assumptions pass, my tests run, and all is good. When the assumptions fail, my tests get skipped, but only after trying to load my expensive context.

How can I avoid the long running time for my test suite?

Things I tried:

  • Subclassing SpringJUnit4ClassRunner, and overriding isTestMethodIgnored.
  • Adding a TestExecutionListener, and throwing the assumption exception in beforeTestClass

These made no impression on Spring, and the context got loaded any way.

Things I didn't try:

  • Lazy init comes with 2.2.X next stable release of spring I think.

Lazy init potentially makes my problem go away, but I feel like there should be some easy spring-test/junit fix that I'm missing.

Thanks in advance for the help.


Solution

  • To me, this sounds like something that you shouldn't do in tests at all. Tests (at least IMHO), are supposed to check the business cases and assume that the environment is set up and ready.

    Maybe it worth to delegate this functionality to build tool and CI.

    Example:

    Define a profile in maven (or whatever build tool you use) that will run integration tests that require VPN. Define profile that will run all the rest of integration tests as well. Activate the profile if some system property is available.

    In CI tool (like Jenkins) as a part of CI even before you run maven, run the script that will check the VPN connection. Based on the results set the system properties and run maven with these properties. The required profiles will be loaded and all the tests / only tests that do not require VPN will be run.

    Update

    If you need to make it work from Spring (and it looks like you prefer this way), Spring has a special annotation called @IfProfileValue

    By default, it matches against system properties and if the value doesn't match the test gets ignored.

    It looks something like this (and note that you can put this annotation on class as well, then it will work for all test methods in the class):

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class MyTestClass {
    
    
        @IfProfileValue(name = "os.name", values = {"Linux"})
        @Test
        public void testMe() {
         // will run only in linux, otherwise
         // won't even try to load an 
         // application context
        ....
        }
      }
    

    This covers the case when you resolve the VPN connectivity externally and run the tests with a property. However, if you want to implement the VPN connectivity check in java, this annotation along not enough because it can work only with Java system properties, so in order to work with custom logic you need to implement org.springframework.test.annotation.ProfileValueSource:

    public class VPNConnectivityProfileValueSource implements ProfileValueSource {
    
      private String vpnEnabled = "true";
      public VPNConnectivityProfileValueSource () {
         // no spring context is available here
         ClassPathResource resource = new ClassPathResource("vpn-config.properties");
    
         if (resource.exists()) {
              // read the VPN address, 
              //   
              //this.testProps = PropertiesLoaderUtils.loadProperties(resource);
              // invoke your utility, check the connectivity, etc. 
              this.vpnEnabled = ...
         }
    
    }
    
      @Override
      public String get(String key) {
        // this is important method, 
        if(key.equals("vpn.enabled") {
           return this.vpnEnabled;  
        } 
        else return System.getProperty(key);
      }
    }
    

    The last thing is to make the test aware of the ProfileValueSource: For this there is another special annotation that you put on the test: @ProfileValueSourceConfiguration(VPNConnectivityProfileValueSource.class)

    All in all it the test can look like this:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @ProfileValueSourceConfiguration(VPNConnectivityProfileValueSource.class)
    @IfProfileValue(name = "vpn.enabled", value = "true")
    public class MyTestClass {
    
       @Test
       public void testMe() {
         ....
       }
    }
    

    All the classes/annotations I've mentioned reside in package org.springframework.test.annotation