web-dev-qa-db-ja.com

junitテストを使用してコマンドライン引数をSpring Bootアプリケーションに渡す

私は非常に基本的な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
_

何かアイデア、パラメータを渡す方法は?

12
divinedragon

テストに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");
    }

}
5

あなたのソリューションは、あなたが提示した方法では機能しないことを残念に思います(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

4
Maciej Marczuk

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>
3
alexbt

コードでは、自動ワイヤスプリングApplicationArgumentsgetSourceArgs()を使用して、コマンドライン引数を取得します。

_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は、スプリングコンテキストの作成直後に実行されるためです。テストクラスでも。したがって、テストが開始される前に実行されます...

2
Torsten

この回答 で述べたように、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部分が必要でした。

0
itzg