単体テスト中にSpring @Valueを設定する


238

プログラムでフォームを検証するために使用される単純なBeanのユニットテストを記述しようとしています。Beanにはアノテーションが付けられ@Componentており、クラス変数が

@Value("${this.property.value}") private String thisProperty;

このクラス内の検証メソッドの単体テストを記述したいのですが、可能であれば、プロパティファイルを使用せずに記述したいと思います。これの背後にある私の推論は、プロパティファイルから取得している値が変更された場合、テストケースに影響を与えないようにするためです。私のテストケースでは、値自体ではなく、値を検証するコードをテストしています。

テストクラス内でJavaコードを使用してJavaクラスを初期化し、そのクラス内にSpring @Valueプロパティを設定して、それを使用してテストする方法はありますか?

このハウツーは近いようですが、まだプロパティファイルを使用しています。私はむしろそれがすべてJavaコードであることを望みます。


ここでは、同様の問題の解決策について説明しました。それが役に立てば幸い。
horizo​​n7

回答:


199

可能であれば、Spring Contextなしでこれらのテストを記述しようとします。Springなしでテストでこのクラスを作成すると、そのフィールドを完全に制御できます。

@valueフィールドを設定するReflectionTestUtilsには、Springsを使用できsetFieldます。プライベートフィールドを設定するメソッドがあります。

@JavaDocを参照:ReflectionTestUtils.setField(java.lang.Object、java.lang.String、java.lang.Object)


2
まさに私がやろうとしていたことと、クラス内で値を設定するために探していたもの、ありがとう!
カイル

2
または、フィールドをデフォルトのアクセス(パッケージ保護)に変更して、Springの依存関係がまったくなくても、テストに簡単にアクセスできるようにします。
Arne Burmeister、2016年

22
例:org.springframework.test.util.ReflectionTestUtils.setField(classUnderTest, "field", "value");
Olivier

4
これらのフィールドをコンストラクターで設定して@Valueから、アノテーションをコンストラクターパラメーターに移動することができます。これにより、コードを手動で作成する場合のテストコードがはるかに簡単になり、Spring Bootは気にしません。
するThorbjörnRavnアンデルセン

これは、単一のテストケースの1つのプロパティをすばやく変更するための最良の答えです。
メンバーサウンド

194

Spring 4.1以降org.springframework.test.context.TestPropertySource、ユニットテストクラスレベルでアノテーションを使用することにより、コード内でプロパティ値を設定できました。プロパティを依存Beanインスタンスに注入する場合でも、このアプローチを使用できます

例えば

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = FooTest.Config.class)
@TestPropertySource(properties = {
    "some.bar.value=testValue",
})
public class FooTest {

  @Value("${some.bar.value}")
  String bar;

  @Test
  public void testValueSetup() {
    assertEquals("testValue", bar);
  }


  @Configuration
  static class Config {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertiesResolver() {
        return new PropertySourcesPlaceholderConfigurer();
    }

  }

}

注:org.springframework.context.support.PropertySourcesPlaceholderConfigurer Springコンテキストにのインスタンスが必要です

編集24-08-2017: SpringBoot 1.4.0以降を使用している場合は@SpringBootTest@SpringBootConfigurationアノテーションとテストを初期化できます。詳細はこちら

SpringBootの場合、次のコードがあります

@SpringBootTest
@SpringBootConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@TestPropertySource(properties = {
    "some.bar.value=testValue",
})
public class FooTest {

  @Value("${some.bar.value}")
  String bar;

  @Test
  public void testValueSetup() {
    assertEquals("testValue", bar);
  }

}

3
ありがとう、ようやく誰かがフィールドの設定方法ではなく値のオーバーライド方法に答えました。PostConstructの文字列フィールドから値を取得するため、文字列値は、構築後ではなく、Springによって設定される必要があります。
テキーラカット2017

@Value( "$ aaaa")-これをConfigクラス内部で使用できますか?
カルペシュソニー

Configは静的クラスなのでわかりません。しかし、お気軽にチェックしてください
Dmytro Boichenko

Mockitoテストクラスで@Valueアノテーションを使用するにはどうすればよいですか?
user1575601

プロパティファイルから値をフェッチするコードを参照しないサービスの統合テストを書いていますが、私のアプリケーションには、プロパティファイルから値をフェッチする構成クラスがあります。したがって、テストを実行すると、未解決のプレースホルダーのエラーが発生します。「$ {spring.redis.port}」と言います
凡例

63

リフレクションによって取得/設定されるプライベートフィールドを乱用しないでください

ここでいくつかの回答で行われているようにリフレクションを使用することは、回避できるものです。
ここには小さな価値がありますが、複数の欠点があります。

  • 実行時にのみリフレクションの問題を検出します(例:もはや存在しないフィールド)
  • カプセル化が必要ですが、表示する必要のある依存関係を隠し、クラスをより不透明でテストしにくくする不透明なクラスは必要ありません。
  • それは悪いデザインを奨励します。今日、宣言し@Value String fieldます。明日あなたが宣言することができます5または10それらのそのクラスに、あなたも、あなたはクラスの設計を減らすことにまっすぐに認識していなくてもよいです。これらのフィールド(コンストラクターなど)を設定するためのより目に見えるアプローチでは、これらのすべてのフィールドを追加する前によく考えて、おそらくそれらを別のクラスにカプセル化して使用します@ConfigurationProperties

クラスを単一と統合の両方でテスト可能にする

Springコンポーネントクラスのプレーンテスト(つまり、実行中のSpringコンテナーなし)と統合テストの両方を記述できるようにするには、このクラスをSpringの有無にかかわらず使用できるようにする必要があります。
必要のないときに単体テストでコンテナーを実行することは、ローカルビルドの速度を低下させる悪い習慣です。これは望ましくありません。
ここでは答えがこの違いを示さないようで、実行中のコンテナーに体系的に依存しているため、この答えを追加しました。

したがって、クラスの内部として定義されているこのプロパティを移動する必要があると思います。

@Component
public class Foo{   
    @Value("${property.value}") private String property;
    //...
}

Springによって注入されるコンストラクタパラメータに:

@Component
public class Foo{   
    private String property;

    public Foo(@Value("${property.value}") String property){
       this.property = property;
    }

    //...         
}

単体テストの例

FooSpringなしでインスタンス化propertyし、コンストラクターのおかげで値を注入できます。

public class FooTest{

   Foo foo = new Foo("dummyValue");

   @Test
   public void doThat(){
      ...
   }
}

統合テストの例

properties属性のおかげで、この簡単な方法でSpring Bootのコンテキストにプロパティを注入できます@SpringBootTest

@SpringBootTest(properties="property.value=dummyValue")
public class FooTest{

   @Autowired
   Foo foo;

   @Test
   public void doThat(){
       ...
   }    
}

代替として使用することもできます @TestPropertySourceが、追加の注釈が追加されます。

@SpringBootTest
@TestPropertySource("property.value=dummyValue")
public class FooTest{ ...}

Spring(Spring Bootなし)では少し複雑になりますが、Spring BootなしでSpringを長い間使用しなかったので、私は愚かなことを言いたくありません。

補足として、@Value設定するフィールドが多い場合@ConfigurationPropertiesは、引数が多すぎるコンストラクタが必要ないため、アノテーションを付けたクラスにフィールドを抽出する方が適切です。


1
すばらしい答えです。ここでのベストプラクティスであることを、コンストラクタ初期化の分野にもあるfinal、すなわち、private String final property
kugo2006

1
誰かがそれを強調したのは素晴らしいことです。Springでのみ機能するようにするには、テスト対象のクラスを@ContextConfigurationに追加する必要があります。
vimterd

53

必要に応じて、Spring Context内でテストを実行し、Spring構成クラス内で必要なプロパティを設定できます。JUnitを使用する場合は、SpringJUnit4ClassRunnerを使用して、次のようなテスト専用の構成クラスを定義します。

テスト中のクラス:

@Component
public SomeClass {

    @Autowired
    private SomeDependency someDependency;

    @Value("${someProperty}")
    private String someProperty;
}

テストクラス:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(classes = SomeClassTestsConfig.class)
public class SomeClassTests {

    @Autowired
    private SomeClass someClass;

    @Autowired
    private SomeDependency someDependency;

    @Before
    public void setup() {
       Mockito.reset(someDependency);

    @Test
    public void someTest() { ... }
}

そして、このテストの構成クラス:

@Configuration
public class SomeClassTestsConfig {

    @Bean
    public static PropertySourcesPlaceholderConfigurer properties() throws Exception {
        final PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
        Properties properties = new Properties();

        properties.setProperty("someProperty", "testValue");

        pspc.setProperties(properties);
        return pspc;
    }
    @Bean
    public SomeClass getSomeClass() {
        return new SomeClass();
    }

    @Bean
    public SomeDependency getSomeDependency() {
        // Mockito used here for mocking dependency
        return Mockito.mock(SomeDependency.class);
    }
}

そうは言っても、このアプローチはお勧めしません。参照用にここに追加しました。私の意見では、より良い方法はMockitoランナーを使用することです。その場合は、Spring内でテストをまったく実行しません。これは、はるかに明確で簡単です。


4
ほとんどのロジックはMockitoでテストする必要があることに同意します。Springを介してテストを実行するよりも、アノテーションの存在と正確さをテストするためのより良い方法があればいいのにと思います。
Altair7852 2015年

29

これは機能するようですが、まだ少し冗長です(まだもっと短いものが欲しいです)。

@BeforeClass
public static void beforeClass() {
    System.setProperty("some.property", "<value>");
}

// Optionally:
@AfterClass
public static void afterClass() {
    System.clearProperty("some.property");
}

2
この答えはSpringにとらわれないのでよりきれいだと思います。カスタムテストランナーを使用する必要があり、単に@TestProperty注釈を追加することができない場合など、さまざまなシナリオでうまく機能します。
raspacorp

これは、Spring統合テストアプローチでのみ機能します。ここにあるいくつかの回答とコメントはMockitoアプローチに傾いていますが、これは確かに機能しません(@Value対応するプロパティが設定されているかどうかに関係なく、s を設定するMockitoには何もないため)
Sander Verhagen

5

構成にPropertyPlaceholderConfigurerを追加するとうまくいきます。

@Configuration
@ComponentScan
@EnableJpaRepositories
@EnableTransactionManagement
public class TestConfiguration {
    @Bean
    public DataSource dataSource() {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        builder.setType(EmbeddedDatabaseType.DERBY);
        return builder.build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPackagesToScan(new String[] { "com.test.model" });
        // Use hibernate
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
        entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
        return entityManagerFactoryBean;
    }

    private Properties getHibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.show_sql", "false");
        properties.put("hibernate.dialect", "org.hibernate.dialect.DerbyDialect");
        properties.put("hibernate.hbm2ddl.auto", "update");
        return properties;
    }

    @Bean
    public JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
         transactionManager.setEntityManagerFactory(
              entityManagerFactory().getObject()
         );

         return transactionManager;
    }

    @Bean
    PropertyPlaceholderConfigurer propConfig() {
        PropertyPlaceholderConfigurer placeholderConfigurer = new PropertyPlaceholderConfigurer();
        placeholderConfigurer.setLocation(new ClassPathResource("application_test.properties"));
        return placeholderConfigurer;
    }
}

そしてテストクラスで

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
public class DataServiceTest {

    @Autowired
    private DataService dataService;

    @Autowired
    private DataRepository dataRepository;

    @Value("${Api.url}")
    private String baseUrl;

    @Test
    public void testUpdateData() {
        List<Data> datas = (List<Data>) dataRepository.findAll();
        assertTrue(datas.isEmpty());
        dataService.updateDatas();
        datas = (List<Data>) dataRepository.findAll();
        assertFalse(datas.isEmpty());
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.