71

If in your TestCase class there is this annotations:

@SpringApplicationConfiguration(classes = {Application.class})

this will cause the Application.class, implementing the CommandLineRunner interface, to run the required method

public void run(String... args) throws Exception

I still think this is, mostly, a not wanted behaviour, since in your test environment you may not want to launch the entire application.

I have in mind two solution to circumvent this problem:

  1. to remove the CommandLineRunner interface from my Application class
  2. to have a different context for testing

Both this solution requires lot of coding. Do you have a more convenient solution?

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
Gorgia666
  • 902
  • 1
  • 6
  • 11
  • 2
    Can you give an example what you want to test? If you want to test something without the whole application than you wouldn't need the annotation. – alexvetter Mar 30 '15 at 11:17
  • 2
    I would like to test the whole application (let's say with all the registered beans). What I dislike is that the whole application starts, due to the `CommandLineRunner` interface implementation. There should be a way to load such an application class without starting the `run` method. – Gorgia666 Apr 01 '15 at 09:23
  • This is exactly my opinion, it is an unwanted side effect or at least SpringRunner should give you the option to prevent it. and start the context when everything in test is setup. – mohamnag Feb 28 '18 at 08:35

7 Answers7

59

Jan's solution can be achieved easier.

In your test class, activate the "test" profile:

@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("test")
public class MyFancyTest {}

In your CommandLineRunner set the profile to NOT test:

@Component
@Profile("!test")
public class JobCommandLineRunner implements CommandLineRunner {}

Then you don't have to manually set the profile in the Application.

Michael P.
  • 651
  • 5
  • 8
  • Does it mean that for an every test file I should place this an additional @ActiveProfiles("test") annotation? – dnim Aug 15 '18 at 06:05
  • Yes @dnim. For every set of tests you want to run. – Michael P. Aug 16 '18 at 10:11
  • 3
    It is no good idea to change production code only to make it testable. Even the profile name "!test" is the very impersonation of a testified production-code. – Sven Döring Sep 18 '19 at 10:30
  • i prefer Jan Petzold's way with `setAdditionalProfiles()` because `@ActiveProfiles` doesnt let you add other profiles. This i often need for running the tests in different environments, ie. on dev machine and within CI server. – elonderin Jul 21 '20 at 09:10
29

As mentioned in the spring documentation you can use @ContextConfiguration with a special initializer:

ConfigDataApplicationContextInitializer is an ApplicationContextInitializer that you can apply to your tests to load Spring Boot application.properties files. You can use it when you do not need the full set of features provided by @SpringBootTest

In this example anyComponent is initialized and properties are injected, but run(args) methods won't be executed. (Application.class is my main spring entry point)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class, 
                      initializers = ConfigDataApplicationContextInitializer.class)
public class ExtractorTest {
    @Autowired
    AnyComponent anyComponent;
    
    @Test
    public void testAnyComponent() {
       anyComponent.anyMethod(anyArgument);
    }
}
Saikat
  • 14,222
  • 20
  • 104
  • 125
jmgonet
  • 1,134
  • 18
  • 18
  • 1
    Ok for CommandLineRunner, but what about ApplicationRunner? I was unable to make a SpringBootApplication (implementing ApplicationRunner) run only once: the first automatically without arguments and the second one manually with my required arguments. – linuxatico Feb 27 '19 at 14:15
27

You can define a test configuration in the same package as your application that looks exactly the same, except that it excludes beans implementing CommandLineRunner. The key here is @ComponentScan.excludeFilters:

@Configuration
@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CommandLineRunner.class))
@EnableAutoConfiguration
public class TestApplicationConfiguration {
}

Then, just replace the configuration on your test:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TestApplicationConfiguration.class)
public class SomeApplicationTest {
    ...
}

No CommandLineRunner will be executed now, because they are not part of the configuration.

Jeffrey Cameron
  • 9,975
  • 10
  • 45
  • 77
Bogdan Calmac
  • 7,993
  • 6
  • 51
  • 64
21

I'm a bit late to the party, but a reasonable approach is to mark the bean with @ConditionalOnProperty, e.g.

@ConditionalOnProperty(prefix = "job.autorun", name = "enabled", havingValue = "true", matchIfMissing = true)
public CommandLineRunner myRunner() {...}

The following annotation will then disable it in tests:

@SpringBootTest(properties = {"job.autorun.enabled=false"})
jihor
  • 2,478
  • 14
  • 28
  • 1
    This is the best solution when you still need to use the annotations on your Main class to configure the application before the test but need to avoid a specific bean from running. Perfect for a command line application that has a bean that "boots up" everything. Thanks. – Marcelus Trojahn Jul 26 '17 at 12:59
5

If you have a mocking framework installed (e.g. MockMVC) you can create a mock instance of the CommandLineRunner implementation, more or less disabling it:

@MockBean private TextProcessor myProcessor;

McAviti
  • 49
  • 1
  • 4
2

Previous answers didn't work wor me. I ended up using different profiles - example for the init method in Spring Boot:

SpringApplication app = new SpringApplication(AppConfig.class);
app.setAdditionalProfiles("production");
app.run(args);

This is not executed during the tests so we're safe here.

All tests have their own profile "test" (which is useful in many other ways, too):

@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("test")
public class MyFancyTest {}

The command-line runner is annotated with the "production" profile so the tests ignore it:

@Component
@Profile("production")
public class JobCommandLineRunner implements CommandLineRunner {}
Jan Petzold
  • 1,561
  • 1
  • 15
  • 24
1

I solve this by not implementing CommandLineRunner. Just get a bean from the context, and call a method on it, passing argv. That way you will get the same result, and the application won't start automatically when running the tests.

Dani
  • 897
  • 6
  • 7