Al escribir pruebas de integración para una base de datos relacional, suele ser útil ejecutar scripts SQL para cambiar el esquema de la base de datos o insertar datos de prueba en tablas. El módulo spring-jdbc proporciona soporte para inicializar una base de datos integrada o existente mediante la ejecución de scripts SQL cuando ApplicationContext se carga en Spring. Para obtener más información, consulte "Soporte de base de datos integrada" y "Prueba de la lógica de acceso a datos utilizando la base de datos integrada".

Aunque es muy razonable inicializar el base de datos para probar una vez al cargar ApplicationContext, a veces aún necesita poder cambiar la base de datos durante las pruebas de integración. Las siguientes secciones explican cómo ejecutar scripts SQL mediante programación y declaración durante las pruebas de integración.

Ejecución de scripts SQL mediante programación

Spring proporciona las siguientes herramientas para ejecutar scripts SQL mediante programación en pruebas de métodos de integración.

  • org.springframework.jdbc.datasource.init.ScriptUtils

  • org.springframework .jdbc.datasource.init.ResourceDatabasePopulator

  • org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests

  • org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests

ScriptUtils proporciona una colección de métodos estáticos para trabajar con scripts SQL y está destinada principalmente para uso interno dentro del marco. Sin embargo, si necesita un control total sobre el análisis y la ejecución de scripts SQL, ScriptUtils puede ser más adecuado que algunas de las otras alternativas que se describen a continuación. Para obtener más información, consulte javadoc en métodos individuales en ScriptUtils.

ResourceDatabasePopulator proporciona una API orientada a objetos para completar, inicializar o completar mediante programación. limpieza de la base de datos utilizando scripts SQL definidos en recursos externos. ResourceDatabasePopulator proporciona opciones para configurar la codificación de caracteres, el separador de declaraciones, los delimitadores de comentarios y los indicadores de manejo de errores utilizados en el análisis y la ejecución de scripts. Cada uno de los parámetros de configuración tiene un valor predeterminado. Para obtener detalles sobre los valores predeterminados, consulte javadoc. Para ejecutar scripts configurados en ResourceDatabasePopulator, puede llamar al método populate(Connection) para ejecutar el populador en java.sql.Connection, o el método execute(DataSource) para ejecutar el módulo de relleno contra javax.sql.DataSource. El siguiente ejemplo especifica secuencias de comandos SQL para el esquema de prueba y los datos de prueba, el delimitador de declaración se establece en @@ y las secuencias de comandos se ejecutan en la relación DataSource:

Java

@Test
void databaseTest() {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScripts(
            new ClassPathResource("test-schema.sql"),
            new ClassPathResource("test-data.sql"));
    populator.setSeparator("@@");
    populator.execute(this.dataSource);
    // ejecuta el código usando el circuito de prueba y los datos
}
Kotlin

@Test
fun databaseTest() {
    val populator = ResourceDatabasePopulator()
    populator.addScripts(
            ClassPathResource("test-schema.sql"),
            ClassPathResource("test-data.sql"))
    populator.setSeparator("@@")
    populator.execute(dataSource)
    // ejecutar el código, utilizando el esquema y los datos de prueba
}

Tenga en cuenta que ResourceDatabasePopulator delega internamente a ScriptUtils la autoridad para analizar y ejecutar scripts SQL. De manera similar, los métodos executeSqlScript(..) en AbstractTransactionalJUnit4SpringContextTests y AbstractTransactionalTestNGSpringContextTests utilizan internamente ResourceDatabasePopulator para ejecutar scripts SQL. Para obtener más información, consulte el Javadoc sobre los distintos métodos executeSqlScript(..).

Ejecución declarativa de scripts SQL utilizando @Sql anotación

Además de los mecanismos anteriores para ejecutar scripts SQL mediante programación, puede configurar scripts SQL de forma declarativa en Spring TestContext Framework. En particular, puede declarar una anotación @Sql en una clase de prueba o método de prueba para configurar sentencias SQL individuales o rutas de recursos de script SQL que se ejecutarán en una base de datos determinada antes o después de la prueba del método de integración. La compatibilidad con la anotación @Sql la proporciona el escucha SqlScriptsTestExecutionListener, que está habilitado de forma predeterminada.

Las declaraciones de anotaciones @Sql en el nivel de método anulan de forma predeterminada las declaraciones de nivel de clase. Sin embargo, a partir de Spring Framework 5.2, esta lógica operativa se puede configurar para cada clase de prueba o cada método de prueba mediante la anotación @SqlMergeMode.
Semántica del recurso de ruta

Cada ruta es interpretado como Resource de Spring. Una ruta simple (por ejemplo, "schema.sql") se considera un recurso de classpath que hace referencia al paquete en el que se define la clase de prueba. Una ruta que comienza con una barra diagonal se considera un recurso de ruta de clase absoluta (por ejemplo, "/org/example/schema.sql"). La ruta que hace referencia a la URL (por ejemplo, la ruta con el prefijo classpath:, file:, http:) se carga utilizando el recurso especificado protocolo.

El siguiente ejemplo muestra cómo utilizar la anotación @Sql a nivel de clase y a nivel de método en una clase de prueba de integración basada en JUnit Jupiter:

Java

@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
    @Test
    void emptySchemaTest() {
         // ejecuta código que utiliza el esquema de prueba sin ningún datos de prueba
    }
    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    void userTest() {
        // ejecutar código usando el esquema de prueba y los datos de prueba
    }
}
    
Kotlin

@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
    @Test
    fun emptySchemaTest() {
         // ejecutar código que utiliza el esquema de prueba sin ningún dato de prueba
    }
    @Test
    @Sql("/test-schema.sql", "/test-user-data.sql")
    fun userTest() {
        // ejecutar código que utiliza circuito de prueba y datos de prueba
    }
}
Detección de secuencias de comandos predeterminada

Si no se especifican secuencias de comandos o sentencias SQL, se intenta determinar una Script default dependiendo de dónde se declara la anotación @Sql. Si no se puede determinar el valor predeterminado, se generará una excepción IllegalStateException.

  • Declaración de nivel de clase: si la clase de prueba anotada es com.ejemplo.MyTest, entonces el script predeterminado correspondiente es classpath:com/example/MyTest.sql.

  • Declaración a nivel de método: Si el método de prueba anotado se llama testMethod() y está definido en la clase com.example.MyTest, entonces el script correspondiente se llamará classpath:com /ejemplo/MiPrueba por defecto. testMethod.sql.

Declaración de múltiples conjuntos de anotaciones @Sql

Si es necesario configure múltiples conjuntos de scripts SQL para una clase o método de prueba determinado, pero con diferentes configuraciones de sintaxis, diferentes reglas de manejo de errores o diferentes fases de ejecución para cada conjunto, puede declarar múltiples instancias de la anotación @Sql . En Java 8, puede utilizar la anotación @Sql como anotación repetida. Alternativamente, puede usar la anotación @SqlGroup como un contenedor explícito para declarar múltiples instancias de la anotación @Sql.

El siguiente ejemplo muestra cómo use la anotación @Sql como una anotación repetida en Java 8:

Java

@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql") void userTest() {
    // ejecutar código usando el esquema de prueba y los datos de prueba
}
Kotlin
// Kotlin aún no admite anotaciones duplicadas con persistencia que no sea SOURCE

En el script presentado en el ejemplo anterior, el script test-schema.sql utiliza una sintaxis diferente para comentarios de una sola línea.

El siguiente ejemplo es idéntico al anterior, excepto que las declaraciones de la anotación @Sql se agrupan en la anotación @SqlGroup. En Java 8 y superiores, el uso de la anotación @SqlGroup es opcional, pero es posible que sea necesario utilizar la anotación @SqlGroup para lograr compatibilidad con otros lenguajes JVM, como Kotlin.

Java

@Test
@SqlGroup({
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql(" /test-user-data.sql")
)}
void userTest() {
    // ejecutar código utilizando el esquema de prueba y los datos de prueba
}
Kotlin

@Test
@SqlGroup(
    Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
    Sql("/test-user-data.sql"))
fun userTest() {
    // ejecutar código usando el circuito de prueba y los datos de prueba
}
Fases de ejecución del script

Los scripts SQL predeterminados se ejecutan antes del método de prueba correspondiente. Sin embargo, si necesita ejecutar un conjunto específico de scripts después de un método de prueba (por ejemplo, para limpiar el estado de la base de datos), puede usar el atributo executionPhase en @Sql. anotación, como se muestra en el siguiente ejemplo:

Java

@Test
@Sql(
    scripts = "create-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
    scripts = "delete-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED),
    executionPhase = AFTER_TEST_METHOD
)
void userTest() {
    // ejecuta el código que indica confirmar datos de prueba
    // a la base de datos fuera de la transacción de prueba
}
Kotlin

@Test
@SqlGroup(
    Sql("create-test-data.sql",
        config = SqlConfig( transactionMode = ISOLATED)),
    Sql("delete-test-data.sql",
        config = SqlConfig(transactionMode = ISOLATED),
        executionPhase = AFTER_TEST_METHOD))
fun userTest() {
    // ejecuta el código que indica capturar los datos de prueba
    // en la base de datos fuera de la transacción de prueba
}

Tenga en cuenta que AISLADO y AFTER_TEST_METHODse importan estáticamente desde Sql.TransactionMode y Sql.ExecutionPhase, respectivamente.

Configuración de script usando @SqlConfig

El análisis de scripts se puede configurar y el manejo de errores usando la anotación @SqlConfig. Si @SqlConfig se declara como una anotación de nivel de clase para una clase de prueba de integración, entonces sirve como una configuración global para todos los scripts SQL en la jerarquía de clases de prueba. Cuando se declara directamente utilizando el atributo config de la anotación @Sql, la anotación @SqlConfig sirve como configuración local para los scripts SQL declarados en el anotación @Sql. Cada atributo en la anotación @SqlConfig tiene un valor predeterminado implícito, que está documentado en el javadoc del atributo correspondiente. Debido a las reglas definidas para los atributos de anotación en la Especificación del lenguaje Java, lamentablemente no es posible establecer un atributo de anotación en null. Por lo tanto, para admitir la anulación de la configuración global heredada, los atributos de anotación @SqlConfig tienen un valor predeterminado explícito de "" (para cadenas), {} (para matrices) o DEFAULT (para enumeraciones). Este enfoque permite que las declaraciones de anotaciones locales @SqlConfig anulen selectivamente atributos individuales de las declaraciones de anotaciones globales @SqlConfig proporcionando un valor distinto de "", {} o DEFAULT. Los atributos globales de la anotación @SqlConfig siempre se heredan a menos que los atributos locales de la anotación @SqlConfig proporcionen un valor explícito distinto de "", {} o DEFAULT. Por lo tanto, la configuración local explícita tiene prioridad sobre la configuración global.

Las opciones de configuración proporcionadas por las anotaciones @Sql y @SqlConfig son equivalentes a las admitidas por ScriptUtils y ResourceDatabasePopulator, pero son un superconjunto de los proporcionados por el elemento de espacio de nombres XML <jdbc:initialize-database/>. Para obtener más información, consulte el javadoc para atributos individuales en las anotaciones @Sql y @SqlConfig.

Gestión de transacciones para anotación @Sql

De forma predeterminada, SqlScriptsTestExecutionListener crea la semántica de transacción deseada para los scripts configurados con @Sql anotación. Específicamente, los scripts SQL se ejecutan sin una transacción dentro de una transacción existente administrada por Spring (por ejemplo, una transacción administrada por TransactionalTestExecutionListener para una prueba marcada con la anotación @Transactional) o dentro de una transacción aislada, dependiendo del valor configurado del atributo transactionMode en la anotación @SqlConfig y de la presencia de un PlatformTransactionManager en el ApplicationContext prueba. Sin embargo, como mínimo, javax.sql.DataSource debe estar presente en el ApplicationContext de la prueba.

Si los algoritmos utilizados por SqlScriptsTestExecutionListener para Si las definiciones de DataSource y PlatformTransactionManager y la salida de la semántica de la transacción no satisfacen sus necesidades, puede especificar nombres explícitos configurando dataSource y transactionManager atributos code en la anotación @SqlConfig. También puede controlar la lógica de propagación de transacciones configurando el atributo transactionMode en la anotación @SqlConfig (por ejemplo, si los scripts deben ejecutarse en una transacción aislada). Aunque una discusión detallada de todas las opciones admitidas para administrar transacciones usando la anotación @Sql está fuera del alcance de esta guía de referencia, el javadoc para @SqlConfig anotación y oyente SqlScriptsTestExecutionListener contienen información detallada y el siguiente ejemplo muestra un escenario de prueba típico usando JUnit Jupiter y pruebas transaccionales con la anotación @Sql:

Java

@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {
    final JdbcTemplate jdbcTemplate;
    @Autowired
    TransactionalSqlScriptsTests(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    @Test
    @Sql("/test-data.sql")
    void usersTest() {
         // verifica el estado en la base de datos de prueba:
        assertNumUsers(2);
        // ejecutar código usando datos de prueba...
    }
    int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }
    void assertNumUsers(int expected) {
        assertEquals(expected, countRowsInTable("user"),
            "Number of rows in the [user] table.");
    }
}
Kotlin

@SpringJUnitConfig(TestDatabaseConfig::class)
@Transactional
class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) {
    val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource)
    @Test
    @Sql("/test-data.sql")
    fun usersTest() {
        // verifica el estado en la base de datos de prueba:
        assertNumUsers(2)
        // ejecuta el código, usando datos de prueba...
    }
    fun countRowsInTable(tableName: String): Int {
        return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
    }
    fun assertNumUsers(expected: Int) {
        assertEquals(expected, countRowsInTable("user"),
                "Number of rows in the [user] table.")
    }
}

Tenga en cuenta que no es necesario borrar la base de datos después de ejecutar usersTest() de método, ya que cualquier cambio realizado en la base de datos (ya sea en el método de prueba o en el script /test-data.sql) se revierte automáticamente mediante el TransactionalTestExecutionListener (ver más detalles en la sección sobre gestión de transacciones).

Fusionar y anular la configuración usando la anotación @SqlMergeMode

A partir de Spring Framework 5.2, puede fusionar declaraciones de anotaciones @Sql a nivel de método con declaraciones a nivel de clase. Por ejemplo, esto le permitiría proporcionar configuración para un esquema de base de datos o algunos datos de prueba generales para una clase de prueba una vez y luego proporcionar datos de prueba adicionales específicos de cada caso para cada método de prueba. Para habilitar la combinación de anotaciones @Sql, anote su clase de prueba o método de prueba con la anotación @SqlMergeMode(MERGE). Para deshabilitar la combinación para un método de prueba específico (o una subclase de prueba específica), puede volver al modo predeterminado usando la anotación @SqlMergeMode(OVERRIDE). Consulte la sección de documentación en anotación @SqlMergeMode para ejemplos e información más detallada.