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
Using junit test to pass command line argument to Spring Boot application
提问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 SpringBootContextLoader
to be more specific) runs your application in its own way. It instantiates SpringApplication
and invokes its run
method without any arguments. It also never uses your main
method implemented in application.
这是因为当您运行测试时,Spring(SpringBootContextLoader
更具体地说是它的测试)以自己的方式运行您的应用程序。它SpringApplication
在run
没有任何参数的情况下实例化并调用其方法。它也从不使用您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 properties
mechanism)
我认为(因为您使用的是 Spring)最简单的解决方案可以使用 spring 配置属性而不是纯命令行参数来实现。(但你应该知道这个解决方案应该用于“配置参数”,因为这是弹簧configuration properties
机制的主要目的)
Reading parameters using @Value
annotation:
使用@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 run
method, 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 IllegalArgumentException
when 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 run
I only assert'ed when that property is enabled, which is defaulted to true
for normal application usage:
...并且,在run
I 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 exitWhenFinished
part 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 Environment
properties instead of commandline arguments. But if you cannot use the springs syntax --argument=value
you 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
您可以编写自己PropertySource
的ConfigurableEnvironment
. 那么你所有的类只需要使用 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 CommandlineRunner
is 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 上下文之后立即执行。即使在考试班。所以它会在你的测试开始之前运行......
回答by deviant
@SpringBootTest
has args
param. You can pass cli arguments there
@SpringBootTest
有args
参数。您可以在那里传递 cli 参数