Skip to content

Commit

Permalink
feature @Sql annotation (#851)
Browse files Browse the repository at this point in the history
  • Loading branch information
timyates authored Oct 23, 2023
1 parent 2b3c5ef commit 00ed0f8
Show file tree
Hide file tree
Showing 61 changed files with 1,395 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
repositories {
mavenCentral()
}
15 changes: 13 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[versions]
micronaut-docs = "2.0.0"
micronaut = "4.1.9"

micronaut-platform = "4.1.4"
micronaut-docs = "2.0.0"
groovy = "4.0.15"

managed-assertj = "3.24.2"
Expand All @@ -15,9 +15,13 @@ managed-spock = "2.3-groovy-4.0"

kotlin = "1.9.10"
graal-svm = "23.1.1"
testcontainers = "1.19.1"

micronaut-test = "4.0.2"
micronaut-data = "4.1.4"
micronaut-hibernate-validator = "4.0.2"
micronaut-logging = "1.1.2"
micronaut-r2dbc = "5.0.1"
micronaut-serde = "2.2.6"
micronaut-spring = "5.0.2"
micronaut-sql = "5.0.3"
Expand All @@ -28,13 +32,16 @@ micronaut-gradle-plugin = "4.1.1"
[libraries]
# Core
micronaut-core = { module = 'io.micronaut:micronaut-core-bom', version.ref = 'micronaut' }
micronaut-platform = { module = 'io.micronaut.platform:micronaut-platform', version.ref = 'micronaut-platform' }

micronaut-data = { module = "io.micronaut.data:micronaut-data-bom", version.ref = "micronaut-data" }
micronaut-hibernate-validator = { module = "io.micronaut.beanvalidation:micronaut-hibernate-validator", version.ref = "micronaut-hibernate-validator" }
micronaut-r2dbc = { module = "io.micronaut.r2dbc:micronaut-r2dbc-bom", version.ref = "micronaut-r2dbc" }
micronaut-serde = { module = "io.micronaut.serde:micronaut-serde-bom", version.ref = "micronaut-serde" }
micronaut-spring = { module = "io.micronaut.spring:micronaut-spring-bom", version.ref = "micronaut-spring" }
micronaut-sql = { module = "io.micronaut.sql:micronaut-sql-bom", version.ref = "micronaut-sql" }
micronaut-reactor = { module = 'io.micronaut.reactor:micronaut-reactor-bom', version.ref = "micronaut-reactor" }
micronaut-test = { module = 'io.micronaut.test:micronaut-test-bom', version.ref = "micronaut-test" }

managed-assertj-core = { module = "org.assertj:assertj-core", version.ref = "managed-assertj" }
managed-hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "managed-hamcrest" }
Expand Down Expand Up @@ -66,3 +73,7 @@ kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref =
groovy = { module = "org.apache.groovy:groovy" }

kotlin-gradle-plugin = { module = 'org.jetbrains.kotlin:kotlin-gradle-plugin', version.ref = 'kotlin' }

testcontainers-bom = { module = "org.testcontainers:testcontainers-bom", version.ref = "testcontainers" }
testcontainers = { module = "org.testcontainers:testcontainers" }
testcontainers-postgresql = { module = "org.testcontainers:postgresql"}
4 changes: 4 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ micronautBuild {
importMicronautCatalog("micronaut-spring")
importMicronautCatalog("micronaut-sql")
importMicronautCatalog("micronaut-reactor")
importMicronautCatalog("micronaut-test")
importMicronautCatalog("micronaut-r2dbc")
}

rootProject.name = 'test-parent'
Expand All @@ -29,3 +31,5 @@ include "test-spock"
include "test-junit5"
include "test-kotest5"
include "test-rest-assured"
include 'test-suite-sql-r2dbc'
include 'test-suite-at-sql-jpa'
87 changes: 87 additions & 0 deletions src/main/docs/guide/sql.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
When performing a test with a backing database, often some data is required in the database prior to running the tests.
As of `micronaut-test` version 4.1.0, there is an annotation api:test.annotation.Sql[].

This annotation can be used to specify the location of one or more sql files to be executed at one of four phases in your test execution:

* `BEFORE_CLASS` - executed once before the tests are run (the default).
* `BEFORE_METHOD` - executed before each test method.
* `AFTER_METHOD` - executed after each test method.
* `AFTER_CLASS` - executed once after all the tests are run.
The files are executed in the order specified in the annotation.

For example given the two SQL scripts

.test/resources/create.sql
[source,sql]
----
include::test-junit5/src/test/resources/create.sql[]
----

and

.test/resources/datasource_1_insert.sql
[source,sql]
----
include::test-junit5/src/test/resources/datasource_1_insert.sql[]
----

We can annotate a test to run these two scripts prior to the test.

[source, java, role="multi-language-sample"]
----
include::{junit5tests}/SqlDatasourceTest.java[tags="clazz"]
----

[source, groovy, role="multi-language-sample"]
----
include::{spocktests}/SqlDatasourceSpec.groovy[tags="clazz"]
----

[source, kotlin, role="multi-language-sample"]
----
include::{kotest5tests}/SqlDatasourceTest.kt[tags="clazz"]
----

<1> Specify the location of the SQL scripts to be executed for a DataSource with the name `default`.

== Phases

The default phase for the scripts to be executed is `BEFORE_CLASS`.
To run the scripts at a different phase, we can specify the `phase` attribute of the annotation.

[source, java]
----
include::test-suite-at-sql-jpa/src/test/java/example/micronaut/TwoProductsThenNoneTest.java[tags="rollback"]
----
<1> A script to run after each test in the specification.

== Named Datasources

If you have multiple datasources configured, you can specify the datasource name to use for the SQL scripts.

[source, java, role="multi-language-sample"]
----
include::{junit5tests}/SqlNamedDatasourceTest.java[tags="clazz"]
----

[source, groovy, role="multi-language-sample"]
----
include::{spocktests}/SqlNamedDatasourceSpec.groovy[tags="clazz"]
----

[source, kotlin, role="multi-language-sample"]
----
include::{kotest5tests}/SqlNamedDatasourceTest.kt[tags="clazz"]
----

<1> Specify the location of the SQL scripts to be executed for a DataSource with the given name.

== R2DBC

For R2DBC, the `Sql` annotation can be used in the same way as for JDBC however it is required to pass the `resourceType` as `ConnectionFactory.class`.

[source,java]
----
include::test-suite-sql-r2dbc/src/test/java/io/micronaut/test/r2dbc/MySqlConnectionTest.java[tags="clazz"]
----
1 change: 1 addition & 0 deletions src/main/docs/guide/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ kotest5:
kotest5ConstructorInjectionCaveats: Constructor Injection Caveats
kotest5RefreshingInjectedBeansBasedOnRequiresUponPropertiesChanges: Refreshing injected beans based on `@Requires` upon properties changes
restAssured: REST Assured
sql: Loading SQL before tests
#spek: Testing with Spek
repository: Repository
3 changes: 3 additions & 0 deletions test-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ plugins {
dependencies {
compileOnly(mn.micronaut.http.server)
compileOnly(libs.junit.jupiter.api)
compileOnly(mnData.micronaut.data.connection.jdbc)
compileOnly(mnR2dbc.r2dbc.spi)
compileOnly(mn.reactor)

api(mn.micronaut.inject)

Expand Down
114 changes: 114 additions & 0 deletions test-core/src/main/java/io/micronaut/test/annotation/Sql.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2017-2023 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.test.annotation;

import io.micronaut.context.annotation.AliasFor;
import io.micronaut.core.annotation.Experimental;

import javax.sql.DataSource;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation that can be applied to a test scenario to execute SQL against a test database prior to the sceario being run.
*
* @since 4.1.0
* @author Tim Yates
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Experimental
@Repeatable(Sql.Sqls.class)
public @interface Sql {

/**
* @return The SQL scripts to execute
*/
@AliasFor(member = "scripts")
String[] value() default {};

/**
* The name of the datasource to use for the SQL scripts.
*
* @return The datasource name
*/
String dataSourceName() default "default";

/**
* The phase of the test to execute the SQL scripts.
* @return The phase
*/
Phase phase() default Phase.BEFORE_ALL;

/**
* Scripts to execute, e.g. {@code "classpath:foo.sql"}.
* @return The SQL scripts to execute
*/
@AliasFor(member = "value")
String[] scripts() default {};

/**
* @return The type of the resource to use for the SQL scripts.
*/
Class<?> resourceType() default DataSource.class;

/**
* Wrapper annotation class to allow multiple Sql annotations per test class or method.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface Sqls {

/**
* @return The SQL scripts to execute
*/
Sql[] value();
}

/**
* The phase of the test to execute the SQL scripts.
*/
enum Phase {
/**
* Execute the SQL before all tests.
*/
BEFORE_ALL,

/**
* Execute the SQL before each test.
*/
BEFORE_EACH,

/**
* Execute the SQL after all tests.
*/
AFTER_ALL,

/**
* Execute the SQL after each test.
*/
AFTER_EACH
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@
import io.micronaut.runtime.context.scope.refresh.RefreshScope;
import io.micronaut.test.annotation.AnnotationUtils;
import io.micronaut.test.annotation.MicronautTestValue;
import io.micronaut.test.annotation.Sql;
import io.micronaut.test.condition.TestActiveCondition;
import io.micronaut.test.context.TestContext;
import io.micronaut.test.context.TestExecutionListener;
import io.micronaut.test.context.TestMethodInterceptor;
import io.micronaut.test.context.TestMethodInvocationContext;
import io.micronaut.test.support.TestPropertyProvider;
import io.micronaut.test.support.TestPropertyProviderFactory;
import io.micronaut.test.support.sql.TestSqlAnnotationHandler;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -192,11 +194,17 @@ public void afterTestExecution(TestContext testContext) throws Exception {
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
fireListeners(TestExecutionListener::beforeTestClass, testContext, false);
if (specDefinition != null && applicationContext != null) {
TestSqlAnnotationHandler.handle(specDefinition, applicationContext, Sql.Phase.BEFORE_ALL);
}
}

@Override
public void afterTestClass(TestContext testContext) throws Exception {
fireListeners(TestExecutionListener::afterTestClass, testContext, true);
if (specDefinition != null && applicationContext != null) {
TestSqlAnnotationHandler.handle(specDefinition, applicationContext, Sql.Phase.AFTER_ALL);
}
}

@Override
Expand All @@ -212,11 +220,17 @@ public void afterSetupTest(TestContext testContext) throws Exception {
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
fireListeners(TestExecutionListener::beforeTestMethod, testContext, false);
if (specDefinition != null && applicationContext != null) {
TestSqlAnnotationHandler.handle(specDefinition, applicationContext, Sql.Phase.BEFORE_EACH);
}
}

@Override
public void afterTestMethod(TestContext testContext) throws Exception {
fireListeners(TestExecutionListener::afterTestMethod, testContext, true);
if (specDefinition != null && applicationContext != null) {
TestSqlAnnotationHandler.handle(specDefinition, applicationContext, Sql.Phase.AFTER_EACH);
}
}

/**
Expand Down
Loading

0 comments on commit 00ed0f8

Please sign in to comment.