6

I'm building a spring boot application. I want to run it like this:

 java -jar myjar.jar inputFile outputFile

How do I write a @SpringBootTest for this? I imagine that using @SpringBootTest would make Spring fail during startup because some of my code would say, "you need to provide an inputFile and outputFile". Is there a way to pass program arguments when using a @SpringBootTest?

I'm inferring from this answer that I may have to use a SpringApplicationBuilder to do this.

I thought I had the answer but I was wrong. This incorrect information may still be useful to some:


(This information is wrong. I think that some arguments can't be referred to in code as properties, but not all. I still don't know how to get the application arguments in a @SpringBootTest) I was confused because I didn't understand the terminology. The annotation has a parameter for "properties". I thought it was to point it at a property file, but the documentation says:

Properties in form key=value that should be added to the Spring Environment before the test runs.

The other piece of the terminology puzzle is that what I called "program arguments" the Spring docs refer to as "properties".

This is some additional relevant documentation: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-application-arguments


This is a workaround (not an answer). You can do something like this:

private SpringApplicationBuilder subject;

@Before
public void setUp() throws Exception {
    subject = new SpringApplicationBuilder(Application.class);
}

@Test
public void requiresInputAndOutput() throws Exception {

    thrown.expect(IllegalStateException.class);
    subject.run();
}

@Test
public void happyPathHasNoErrors() throws Exception {

    subject.run(EXISTING_INPUT_FILE, "does_not_exist");
}

I don't like this very much. It prevents me from using @Autowired elsewhere in my test.

Community
  • 1
  • 1
Daniel Kaplan
  • 62,768
  • 50
  • 234
  • 356

5 Answers5

9

if your program arguments is

--arg1=val1

before springboot version 2.2.0 you can use

@SpringBootTest({"arg1=val1"})

after springboot 2.2.0 you can use

@SpringBootTest(args={"--arg1=val1"})
ryanlee
  • 341
  • 3
  • 9
3

I had the same issue. Out of the box, the SpringBootContextLoader (used by default with @SpringBootTest) always runs your app without any arguments. However, you can provide your own bootstrapper, which can then in turn provide your own SpringApplication that gets called to run your test. From there, you can override the run(String... args) method to provide whatever arguments you want.

For example, given a simple application:

@SpringBootApplication
public class Main {
    public static void main(final String[] args) {
        new SpringApplicationBuilder(Main.class).run(args);
    }

    @Bean
    public ApplicationRunner app() {
        return args -> System.out.println(args.getOptionNames());
    }
}

You can inject test arguments with:

@ContextConfiguration(classes = Main.class)
@ExtendWith(SpringExtension.class)
@BootstrapWith(RestartAppsTest.Bootstrapper.class)
class RestartAppsTest {

    static class Bootstrapper extends SpringBootTestContextBootstrapper {
        static class ArgumentSupplyingContextLoader extends SpringBootContextLoader {
            @Override
            protected SpringApplication getSpringApplication() {
                return new SpringApplication() {
                    @Override
                    public ConfigurableApplicationContext run(String... args) {
                        return super.run("--restart");
                    }
                };
            }
        }

        @Override
        protected Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass) {
            return ArgumentSupplyingContextLoader.class;
        }
    }

    @Test
    void testRestart() {
        // 
    }
}

It's obviously a bit verbose, but it works. You could clean it up and make a nicer/reusable bootstrapper that looked for your own annotation (or possibly reuse JUnit Jupiter's @Arguments) that declared what arguments to supply (instead of hardcoding them).

  • 1
    I opened a PR to add support for this to `@SpringBootTest`. https://github.com/spring-projects/spring-boot/pull/14823 – Justin Griffin Oct 15 '18 at 03:54
  • Building off of this, you can actually just use `@SpringBootTest` and `@BootstrapWith`, no need to drop down to `@ContextConfiguration` and a superfluous (as of Spring Boot 2.1) `@ExtendWith`. – phillipuniverse Jan 13 '20 at 18:20
2

For me working example is

@SpringBootTest(args ={"--env", "test"})
class QuickFixServerAppTest {

    @Test
    void loadContextTest() {
    }
}

is the same as passing the

--env test

argument when starting Spring

dexter32
  • 73
  • 6
1

You can use @SpringBootTest(classes=Application.class, args ={inputFile, outputFile}) if your app's main method looks a little different like

public static void main(String[] args){ 
     SpringApplication.run(Application.class, args);
}
tscafie
  • 61
  • 8
  • How do you know, how the main method looks like? – andy Oct 30 '20 at 21:55
  • it's just the main method in the Spring application – tscafie Nov 03 '20 at 00:48
  • Yes, usually this is a main method of a Sping Boot application. But I think @Daniel has added his own code which checks the arguments somehow... So actually we can't know how to fix his problem. – andy Nov 04 '20 at 07:44
  • We don't need to know what the main method looks like. `@SpringBootTest(classes=Application.class, args ={inputFile, outputFile})` simulates the behavior that @Daniel was asking for where the SpringBootTest receives the args `inputFile` and `outputFile` – tscafie Dec 03 '20 at 08:12
0

Normally you're writing tests for your services; not the boot strapper. Spring Boot will pass command line parameters to your classes - perhaps using the @Value annotation, which in turn will be parameters to your service. Consider testing your services using the SpringRunner. Here's an example from my code base.

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
    Neo4jConfigTest.class,
    FdTemplate.class,
    FdServerIo.class,
    MapBasedStorageProxy.class})
@ActiveProfiles({"dev", "fd-auth-test", "fd-client"})
public class TestEntityLinks  {

@Autowired
private ContentModelService contentModelService;

@Autowired
private BatchService batchService;

@Test
public void doSomething () { ... }
Mike Holdsworth
  • 1,088
  • 11
  • 12
  • Maybe I don't understand your answer, but I don't know how to take action on it. Seems like you're saying, "Don't test this way. Here's how you should test the *other* components of your app though." In the context of my question, I feel my workaround is more helpful. – Daniel Kaplan Feb 22 '17 at 17:34
  • Maybe, but coding with Spring is easier when you follow the conventions. There is no need to build & run the application, for a test, in the way you're describing. SpringRunner will build the application context and let you autowire your services. Likewise @WebAppConfiguration is used to establish a testable web-server context. – Mike Holdsworth Feb 23 '17 at 00:55
  • 1
    I want to test that it errors on start up if you don't pass these command line arguments. – Daniel Kaplan Feb 23 '17 at 04:14
  • Have you looked at implementing CommandLineRunner for your SB app? You implement the run method and can have access to the args. HTH – Mike Holdsworth Feb 24 '17 at 20:55
  • Yeah, actually I implemented `ApplicationRunner` – Daniel Kaplan Feb 25 '17 at 01:30