環境プロファイルを使用したコンテキスト設定
Spring Framework には、環境とプロファイル(別名「Bean 定義プロファイル」)の概念に対するファーストクラスのサポートがあり、さまざまなテストシナリオで特定の Bean 定義プロファイルをアクティブにするように統合テストを構成できます。これは、テストクラスに @ActiveProfiles
アノテーションを付けて、テスト用に ApplicationContext
をロードするときにアクティブ化する必要があるプロファイルのリストを提供することで実現されます。
@ActiveProfiles は SmartContextLoader SPI のどの実装でも使用できますが、@ActiveProfiles は古い ContextLoader SPI の実装ではサポートされていません。 |
XML 構成と @Configuration
クラスを使用した 2 つの例を検討してください。
<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<bean id="transferService"
class="com.bank.service.internal.DefaultTransferService">
<constructor-arg ref="accountRepository"/>
<constructor-arg ref="feePolicy"/>
</bean>
<bean id="accountRepository"
class="com.bank.repository.internal.JdbcAccountRepository">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="feePolicy"
class="com.bank.service.internal.ZeroFeePolicy"/>
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script
location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
<beans profile="default">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
</jdbc:embedded-database>
</beans>
</beans>
Java
Kotlin
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// test the transferService
}
}
TransferServiceTest
が実行されると、その ApplicationContext
はクラスパスのルートにある app-config.xml
構成ファイルからロードされます。app-config.xml
を調べると、accountRepository
Bean が dataSource
Bean に依存していることがわかります。ただし、dataSource
はトップレベル Bean として定義されていません。代わりに、dataSource
は、production
プロファイル、dev
プロファイル、default
プロファイルで 3 回定義されます。
TransferServiceTest
に @ActiveProfiles("dev")
のアノテーションを付けることにより、Spring TestContext フレームワークに、{"dev"}
に設定されたアクティブなプロファイルで ApplicationContext
をロードするよう指示します。その結果、組み込みデータベースが作成され、テストデータが入力され、accountRepository
Bean が開発 DataSource
への参照で接続されます。これはおそらく統合テストで必要なものです。
Bean を default
プロファイルに割り当てると便利な場合があります。デフォルトプロファイル内の Bean は、他のプロファイルが特にアクティブ化されていない場合にのみ含まれます。これを使用して、アプリケーションのデフォルト状態で使用される「フォールバック」Bean を定義できます。例: dev
および production
プロファイルのデータソースを明示的に提供できますが、どちらもアクティブでない場合は、メモリ内データソースをデフォルトとして定義できます。
次のコードリストは、XML の代わりに @Configuration
クラスを使用して同じ構成および統合テストを実装する方法を示しています。
Java
Kotlin
@Configuration
@Profile("dev")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("dev")
class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
Java
Kotlin
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Configuration
@Profile("production")
class JndiDataConfig {
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
Java
Kotlin
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
Java
Kotlin
@Configuration
public class TransferServiceConfig {
@Autowired DataSource dataSource;
@Bean
public TransferService transferService() {
return new DefaultTransferService(accountRepository(), feePolicy());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public FeePolicy feePolicy() {
return new ZeroFeePolicy();
}
}
@Configuration
class TransferServiceConfig {
@Autowired
lateinit var dataSource: DataSource
@Bean
fun transferService(): TransferService {
return DefaultTransferService(accountRepository(), feePolicy())
}
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun feePolicy(): FeePolicy {
return ZeroFeePolicy()
}
}
Java
Kotlin
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
@SpringJUnitConfig(
TransferServiceConfig::class,
StandaloneDataConfig::class,
JndiDataConfig::class,
DefaultDataConfig::class)
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// test the transferService
}
}
このバリエーションでは、XML 構成を 4 つの独立した @Configuration
クラスに分割しました。
TransferServiceConfig
:@Autowired
を使用して、依存性注入によりdataSource
を取得します。StandaloneDataConfig
: 開発者テストに適した組み込みデータベースのdataSource
を定義します。JndiDataConfig
: 本番環境で JNDI から取得されるdataSource
を定義します。DefaultDataConfig
: アクティブなプロファイルがない場合に、デフォルトの組み込みデータベースのdataSource
を定義します。
XML ベースの構成例と同様に、TransferServiceTest
に @ActiveProfiles("dev")
のアノテーションを付けますが、今回は @ContextConfiguration
アノテーションを使用して 4 つの構成クラスすべてを指定します。テストクラスの本体自体は完全に変更されていません。
多くの場合、特定のプロジェクト内の複数のテストクラスで単一のプロファイルセットが使用されます。@ActiveProfiles
アノテーションの重複した宣言を避けるために、ベースクラスで @ActiveProfiles
を 1 回宣言することができ、サブクラスはベースクラスから @ActiveProfiles
構成を自動的に継承します。次の例では、@ActiveProfiles
の宣言(およびその他のアノテーション)が抽象スーパークラス AbstractIntegrationTest
に移動されています。
Spring Framework 5.3 以降、テスト構成は、囲んでいるクラスから継承することもできます。詳細については、@Nested テストクラスの構成を参照してください。 |
Java
Kotlin
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
@SpringJUnitConfig(
TransferServiceConfig::class,
StandaloneDataConfig::class,
JndiDataConfig::class,
DefaultDataConfig::class)
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
Java
Kotlin
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
// "dev" profile inherited from superclass
class TransferServiceTest : AbstractIntegrationTest() {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// test the transferService
}
}
@ActiveProfiles
は、次の例に示すように、アクティブプロファイルの継承を無効にするために使用できる inheritProfiles
属性もサポートしています。
Java
Kotlin
// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
// test body
}
// "dev" profile overridden with "production"
@ActiveProfiles("production", inheritProfiles = false)
class ProductionTransferServiceTest : AbstractIntegrationTest() {
// test body
}
さらに、テストのアクティブなプロファイルを、宣言的にではなくプログラムによって解決することが必要になる場合があります。たとえば、次のものに基づいています。
現在のオペレーティングシステム。
テストが継続的インテグレーションビルドサーバーで実行されているかどうか。
特定の環境変数の存在。
カスタムクラスレベルのアノテーションの存在。
その他の懸念。
アクティブな Bean 定義プロファイルをプログラムで解決するために、カスタム ActiveProfilesResolver
を実装し、@ActiveProfiles
の resolver
属性を使用してそれを登録できます。詳細については、対応する javadoc を参照してください。次の例は、カスタム OperatingSystemActiveProfilesResolver
を実装および登録する方法を示しています。
Java
Kotlin
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
// test body
}
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver::class,
inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
// test body
}
Java
Kotlin
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {
@Override
public String[] resolve(Class<?> testClass) {
String profile = ...;
// determine the value of profile based on the operating system
return new String[] {profile};
}
}
class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver {
override fun resolve(testClass: Class<*>): Array<String> {
val profile: String = ...
// determine the value of profile based on the operating system
return arrayOf(profile)
}
}