Java 使用 junit 测试将命令行参数传递给 Spring Boot 应用程序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39144136/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 20:54:22  来源:igfitidea点击:

Using junit test to pass command line argument to Spring Boot application

javaspringunit-testingjunitspring-boot

提问by divinedragon

I have a very basic Spring Boot application, which is expecting an argument from command line, and without it doesn't work. Here is the code.

我有一个非常基本的 Spring Boot 应用程序,它需要来自命令行的参数,没有它就不起作用。这是代码。

@SpringBootApplication
public class Application implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(Application.class);

    @Autowired
    private Reader reader;

    @Autowired
    private Writer writer;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) throws Exception {

        Assert.notEmpty(args);

        List<> cities = reader.get("Berlin");
         writer.write(cities);
    }
}

Here is my JUnit test class.

这是我的 JUnit 测试类。

@RunWith(SpringRunner.class)
@SpringBootTest
public class CityApplicationTests {

    @Test
    public void contextLoads() {
    }
}

Now, Assert.notEmpty()mandates for passing an argument. However, now, I am writing JUnit test for the same. But, I get following exception raise from the Assert.

现在,Assert.notEmpty()要求传递参数。但是,现在,我正在为此编写 JUnit 测试。但是,我从Assert.

2016-08-25 16:59:38.714 ERROR 9734 --- [           main] o.s.boot.SpringApplication               : Application startup failed

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:801) ~[spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:782) ~[spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:769) ~[spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) ~[spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:111) [spring-boot-test-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.boot.test.autoconfigure.AutoConfigureReportTestExecutionListener.prepareTestInstance(AutoConfigureReportTestExecutionListener.java:46) [spring-boot-test-autoconfigure-1.4.0.RELEASE.jar:1.4.0.RELEASE]
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runReflectiveCall(SpringJUnit4ClassRunner.java:287) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE]
    at org.junit.runners.ParentRunner.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.access
@SpringBootApplication
public class Application implements CommandLineRunner {

    @Value("${myCustomArgs.customArg1}")
    private String customArg1;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) throws Exception {

        Assert.notNull(customArg1);
        //...
    }
}
0(ParentRunner.java:58) [junit-4.12.jar:4.12] at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12] at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) [spring-test-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) [.cp/:na] at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) [.cp/:na] at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) [.cp/:na] at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) [.cp/:na] at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) [.cp/:na] at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) [.cp/:na] Caused by: java.lang.IllegalArgumentException: [Assertion failed] - this array must not be empty: it must contain at least 1 element at org.springframework.util.Assert.notEmpty(Assert.java:222) ~[spring-core-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.util.Assert.notEmpty(Assert.java:234) ~[spring-core-4.3.2.RELEASE.jar:4.3.2.RELEASE] at com.deepakshakya.dev.Application.run(Application.java:33) ~[classes/:na] at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798) ~[spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE] ... 32 common frames omitted

Any idea, how to pass the parameter?

任何想法,如何传递参数?

回答by Maciej Marczuk

I'm affraid that your solution will not work in a way that you presented (until you implement your own test framework for Spring).

我担心您的解决方案不会以您提供的方式工作(直到您为 Spring 实现自己的测试框架)。

This is because when you are running tests, Spring (its test SpringBootContextLoaderto be more specific) runs your application in its own way. It instantiates SpringApplicationand invokes its runmethod without any arguments. It also never uses your mainmethod implemented in application.

这是因为当您运行测试时,Spring(SpringBootContextLoader更具体地说是它的测试)以自己的方式运行您的应用程序。它SpringApplicationrun没有任何参数的情况下实例化并调用其方法。它也从不使用您main在应用程序中实现的方法。

However, you could refactor your application in a way that it'll be possible to test it.

但是,您可以以一种可以对其进行测试的方式重构您的应用程序。

I think (since you are using Spring) the easiest solution could be implemented using spring configuration properties instead of pure command line arguments. (But you should be aware that this solution should be used rather for "configuration arguments", because that's the main purpose of springs configuration propertiesmechanism)

我认为(因为您使用的是 Spring)最简单的解决方案可以使用 spring 配置属性而不是纯命令行参数来实现。(但你应该知道这个解决方案应该用于“配置参数”,因为这是弹簧configuration properties机制的主要目的)

Reading parameters using @Valueannotation:

使用@Value注解读取参数:

@RunWith(SpringRunner.class)
@SpringBootTest({"myCustomArgs.customArg1=testValue"})
public class CityApplicationTests {

    @Test
    public void contextLoads() {
    }
}

Sample test:

样品测试:

@RunWith(MockitoJUnitRunner.class)
public class ApplicationTest {

    @InjectMocks
    private Application app = new Application();

    @Mock
    private Reader reader;

    @Mock
    private Writer writer;

    @Test(expected = IllegalArgumentException.class)
    public void testNoArgs() throws Exception {
        app.run();
    }

    @Test
    public void testWithArgs() throws Exception {
        List list = new ArrayList();
        list.add("test");
        Mockito.when(reader.get(Mockito.anyString())).thenReturn(list);

        app.run("myarg");

        Mockito.verify(reader, VerificationModeFactory.times(1)).get(Mockito.anyString());
        Mockito.verify(writer, VerificationModeFactory.times(1)).write(list);
    }
}

And when running your command line app just add your custom params:

在运行命令行应用程序时,只需添加自定义参数:

--myCustomArgs.customArg1=testValue

--myCustomArgs.customArg1=testValue

回答by alexbt

I would leave SpringBoot out of the equation.

我会将 SpringBoot 排除在等式之外。

You simply need to test the runmethod, without going through Spring Boot, since your goal is not to test spring boot, isn't it ? I suppose, the purpose of this test is more for regression, ensuring that your application always throws an IllegalArgumentExceptionwhen no args are provided? Good old unit test still works to test a single method:

您只需要测试该run方法,而无需通过 Spring Boot,因为您的目标不是测试 Spring Boot,不是吗?我想,这个测试的目的更多是为了回归,确保你的应用程序IllegalArgumentException在没有提供参数时总是抛出一个?好的旧单元测试仍然可以测试单个方法:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.0</version>
    <scope>test</scope>
</dependency>

I used Mockito to inject mocks for Reader and Writer:

我使用 Mockito 为 Reader 和 Writer 注入模拟:

package my.package.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
class AsgardBpmClientApplicationIT {

    @Autowired
    ApplicationContext ctx;

    @Test
    public void testRun() {
        CommandLineRunner runner = ctx.getBean(CommandLineRunner.class);
        runner.run ( "-k", "arg1", "-i", "arg2");
    }

}

回答by Philippe Sevestre

I′ve managed to find a way to create Junit tests that worked fine with SpringBoot by injecting the ApplicationContext in my test and calling a CommandLineRunner with the required parameters.

通过在我的测试中注入 ApplicationContext 并使用所需的参数调用 CommandLineRunner,我设法找到了一种方法来创建与 SpringBoot 配合良好的 Junit 测试。

The final code looks like that:

最终代码如下所示:

@ConfigurationProperties("app") @Component @Data
public class AppProperties {
    boolean failOnEmptyFileList = true;
    boolean exitWhenFinished = true;
}

回答by itzg

As mentioned in this answer, Spring Boot currently doesn't offer a way to intercept/replace the DefaultApplicationArgumentsthat it uses. A natural-Boot-way that I used to solve this was to enhance my runner logic and use some autowired properties.

本答案所述,Spring Boot 目前不提供拦截/替换它使用的DefaultApplicationArguments 的方法。我用来解决这个问题的一种自然引导方式是增强我的跑步者逻辑并使用一些自动装配的属性。

First, I created a properties component:

首先,我创建了一个属性组件:

@Service
public class Loader implements ApplicationRunner {

    private AppProperties properties;

    @Autowired
    public Loader(AppProperties properties) {
        this.properties = properties;
    }
    ...

...autowired the properties component into my runner:

...将属性组件自动装配到我的跑步者中:

@Override
public void run(ApplicationArguments args) throws Exception {
    if (properties.isFailOnEmptyFileList()) {
        Assert.notEmpty(args.getNonOptionArgs(), "Pass at least one filename on the command line");
    }

    // ...do some loading of files and such

    if (properties.isExitWhenFinished()) {
        System.exit(0);
    }
}

...and, in the runI only assert'ed when that property is enabled, which is defaulted to truefor normal application usage:

...并且,在runI only assert'ed 启用该属性时,默认true为正常应用程序使用:

@RunWith(SpringRunner.class)
@SpringBootTest(properties = {
        "app.failOnEmptyFileList=false",
        "app.exitWhenFinished=false"
})
public class InconsistentJsonApplicationTests {

    @Test
    public void contextLoads() {
    }

}

With that, I can tweak those properties to execute in a unit test friendly manner:

有了这个,我可以调整这些属性以以单元测试友好的方式执行:

public CityApplicationService(ApplicationArguments args, Writer writer){        
    public void writeFirstArg(){
        writer.write(args.getSourceArgs()[0]);
    }
}

I needed the exitWhenFinishedpart since my particular runner normally calls System.exit(0)and exiting that way leaves the unit test in a semi-failed state.

我需要这个exitWhenFinished部分,因为我的特定跑步者通常会调用System.exit(0)并退出这种方式使单元测试处于半失败状态。

回答by Torsten

In your code autowire springs ApplicationArguments. Use getSourceArgs()to retrieve the commandline arguments.

在您的代码中 autowire springs ApplicationArguments。使用getSourceArgs()检索命令行参数。

@RunWith(SpringRunner.class)
@SpringBootTest
public class CityApplicationTests {
@MockBean
private ApplicationArguments args;

    @Test
    public void contextLoads() {
        // given
        Mockito.when(args.getSourceArgs()).thenReturn(new String[]{"Berlin"});

        // when
        ctx.getBean(CityApplicationService.class).writeFirstArg();

        // then
        Mockito.verify(writer).write(Matchers.eq("Berlin"));

    }
}

In your test mock the ApplicationArguments.

在您的测试中模拟 ApplicationArguments。

public class ArgsPropertySource extends PropertySource<Map<String, String>> {

    ArgsPropertySource(List<CmdArg> cmdArgs, List<String> arguments) {
        super("My special commandline arguments", new HashMap<>());

        // CmdArgs maps the property name to the argument value.
        cmdArgs.forEach(cmd -> cmd.mapArgument(source, arguments));
    }

    @Override
    public Object getProperty(String name) {
        return source.get(name);
    }
}


public class SetupArgs {

    SetupArgs(ConfigurableEnvironment env, ArgsMapping mapping) {           
        // In real world, this code would be in an own method.
        ArgsPropertySource = new ArgsPropertySource(mapping.get(), args.getSourceArgs());
        environment
            .getPropertySources()
            .addFirst(propertySource);
    }
}


Like Maciej Marczuksuggested, I also prefer to use Springs Environmentproperties instead of commandline arguments. But if you cannot use the springs syntax --argument=valueyou could write an own PropertySource, fill it with your commandline arguments syntax and add it to the ConfigurableEnvironment. Then all your classes only need to use springs Environment properties.

就像Maciej Marczuk建议的那样,我也更喜欢使用 SpringsEnvironment属性而不是命令行参数。但是,如果您不能使用 springs 语法,--argument=value您可以编写自己PropertySourceConfigurableEnvironment. 那么你所有的类只需要使用 springs Environment 属性。

E.g.

例如

##代码##

BTW:

顺便提一句:

Since I do not have enough reputation points to comment an answer, I would still like to leave a hard learned lesson here:

由于我没有足够的声望点来评论答案,我仍然想在这里留下一个深刻的教训:

The CommandlineRunneris not such a good alternative. Since its run()method alwyas gets executed right after the creation of the spring context. Even in a test-class. So it will run, before your Test started ...

CommandlineRunner不是一个很好的选择。因为它的run()方法 alwyas 在创建 spring 上下文之后立即执行。即使在考试班。所以它会在你的测试开始之前运行......