私は非常に基本的な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);
}
}
_
これが私のJUnitテストクラスです。
_@RunWith(SpringRunner.class)
@SpringBootTest
public class CityApplicationTests {
@Test
public void contextLoads() {
}
}
_
現在、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$1.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$3.run(ParentRunner.Java:290) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$1.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$000(ParentRunner.Java:58) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$2.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
_
何かアイデア、パラメータを渡す方法は?
テストにApplicationContextを挿入し、必要なパラメーターを指定してCommandLineRunnerを呼び出すことで、SpringBootで正常に機能するJunitテストを作成する方法を見つけることができました。
最終的なコードは次のようになります。
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");
}
}
あなたのソリューションは、あなたが提示した方法では機能しないことを残念に思います(Spring用の独自のテストフレームワークを実装するまで)。
これは、テストを実行しているときに、Spring(より具体的にはテストSpringBootContextLoader
)が独自の方法でアプリケーションを実行するためです。 SpringApplication
をインスタンス化し、引数なしでrun
メソッドを呼び出します。また、アプリケーションに実装されているmain
メソッドも使用しません。
ただし、テストできるようにアプリケーションをリファクタリングできます。
(Springを使用しているので)最も簡単なソリューションは、純粋なコマンドライン引数ではなく、Spring構成プロパティを使用して実装できると思います。 (ただし、このソリューションは「構成引数」に使用する必要があることに注意する必要があります。これはスプリングconfiguration properties
メカニズムの主な目的だからです)
@Value
アノテーションを使用したパラメーターの読み取り:
@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);
//...
}
}
サンプルテスト:
@RunWith(SpringRunner.class)
@SpringBootTest({"myCustomArgs.customArg1=testValue"})
public class CityApplicationTests {
@Test
public void contextLoads() {
}
}
コマンドラインアプリを実行するときは、カスタムパラメーターを追加するだけです。
--myCustomArgs.customArg1=testValue
SpringBootは方程式から除外します。
目的はスプリングブートをテストすることではないので、スプリングブートを経由せずに、単にrun
メソッドをテストする必要がありますか?私は、このテストの目的は回帰のためであり、引数が提供されていないときにアプリケーションが常にIllegalArgumentException
をスローすることを保証すると思いますか?古き良きユニットテストは、単一のメソッドをテストするために引き続き機能します。
@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);
}
}
Mockitoを使用して、リーダーとライターのモックを挿入しました。
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.0</version>
<scope>test</scope>
</dependency>
コードでは、自動ワイヤスプリングApplicationArguments
。 getSourceArgs()
を使用して、コマンドライン引数を取得します。
_public CityApplicationService(ApplicationArguments args, Writer writer){
public void writeFirstArg(){
writer.write(args.getSourceArgs()[0]);
}
}
_
テストでApplicationArgumentsをモックします。
_@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"));
}
}
_
Maciej Marczukのように、コマンドライン引数の代わりにSprings Environment
プロパティを使用することも好みます。ただし、スプリングの構文_--argument=value
_を使用できない場合は、独自のPropertySource
を記述し、コマンドライン引数の構文を入力してConfigurableEnvironment
に追加できます。その後、すべてのクラスで使用する必要があるのは、スプリングの環境プロパティのみです。
例えば。
_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);
}
}
_
ところで:
私は答えをコメントするのに十分な評判ポイントを持っていないので、私はまだここでハード学んだレッスンを残したいと思います:
CommandlineRunner
は、このような適切な代替手段ではありません。 run()
メソッドalwyasは、スプリングコンテキストの作成直後に実行されるためです。テストクラスでも。したがって、テストが開始される前に実行されます...
この回答 で述べたように、Spring Bootは現在、使用する DefaultApplicationArguments をインターセプト/置換する方法を提供していません。これを解決するために使用した自然なブート方法は、ランナーロジックを強化し、自動配線されたプロパティを使用することでした。
まず、プロパティコンポーネントを作成しました。
_@ConfigurationProperties("app") @Component @Data
public class AppProperties {
boolean failOnEmptyFileList = true;
boolean exitWhenFinished = true;
}
_
...プロパティコンポーネントをランナーに自動接続しました。
_@Service
public class Loader implements ApplicationRunner {
private AppProperties properties;
@Autowired
public Loader(AppProperties properties) {
this.properties = properties;
}
...
_
...そして、run
では、そのプロパティが有効になっているときにのみアサートします。これは、通常のアプリケーションの使用ではデフォルトでtrue
になります:
_@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);
}
}
_
それにより、これらのプロパティを微調整して、単体テストに適した方法で実行できます。
_@RunWith(SpringRunner.class)
@SpringBootTest(properties = {
"app.failOnEmptyFileList=false",
"app.exitWhenFinished=false"
})
public class InconsistentJsonApplicationTests {
@Test
public void contextLoads() {
}
}
_
私の特定のランナーは通常System.exit(0)
を呼び出し、その方法で終了すると単体テストが半失敗状態のままになるため、exitWhenFinished
部分が必要でした。