Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotation to load SQL before a test @Sql #851

Merged
merged 26 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c0dab7d
Add Spock support
timyates Sep 18, 2023
c48f5cc
Revert whitespace change
timyates Sep 18, 2023
359d59a
Remove anotation alias. Not sure how it works
timyates Sep 18, 2023
4b52572
Add JUnit and JPA support
timyates Sep 18, 2023
08c5813
Add kotest
timyates Sep 18, 2023
29e6f72
Documentation
timyates Sep 18, 2023
857a141
Update gradle/libs.versions.toml
sdelamo Sep 19, 2023
b3df3fa
Switch to use specDefinition and move code up out of the individual i…
timyates Sep 19, 2023
54c7000
Unused imports
timyates Sep 19, 2023
94c1994
Support R2DBC and add Graal test for it
timyates Sep 20, 2023
7026186
Add javadoc and remove un-required value finagling
timyates Sep 20, 2023
d167f5e
Unused import
timyates Sep 20, 2023
2807e8f
Move Sql invocation to fall inside transactions
timyates Sep 20, 2023
6a34932
Mark things as experimental
timyates Sep 20, 2023
d9ec210
Undo irrelevant changes
timyates Sep 20, 2023
f6fef78
Add internal
timyates Sep 21, 2023
93ce4c5
Switch versions around
timyates Sep 21, 2023
785a04f
Switch to just using generic bean loading
timyates Sep 21, 2023
97b886f
Address feedback
timyates Sep 21, 2023
07ac19c
dont import instead of set version for test-suite
timyates Sep 21, 2023
6870abc
Documentation
timyates Sep 21, 2023
800356e
add nullability annotations
sdelamo Sep 21, 2023
10cee0a
Merge branch 'master' into sql-annotation
sdelamo Oct 13, 2023
e151975
Sql annotation test project (#867)
sdelamo Oct 20, 2023
d26edd3
Merge branch 'master' into sql-annotation
timyates Oct 20, 2023
4aa68ff
Sonar smells
timyates Oct 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ graal-svm = "23.0.1"

micronaut-data = "4.1.2"
micronaut-hibernate-validator = "4.0.2"
micronaut-logging = "1.0.0"
sdelamo marked this conversation as resolved.
Show resolved Hide resolved
micronaut-serde = "2.2.4"
micronaut-spring = "5.0.2"
micronaut-sql = "5.0.1"
Expand Down
41 changes: 41 additions & 0 deletions src/main/docs/guide/sql.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
:icons: font
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 prior to the test.
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> The annotation is used to specify the location of the SQL scripts to be executed. (The default datasource name is `default`, but this can be overriden by setting the `datasourceName` property for the annotation)
1 change: 1 addition & 0 deletions src/main/docs/guide/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ spock: Testing with Spock
junit5: Testing with JUnit 5
kotest5: Testing with Kotest 5
restAssured: REST Assured
sql: Loading SQL before tests
#spek: Testing with Spek
repository: Repository
1 change: 1 addition & 0 deletions test-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {
dependencies {
compileOnly(mn.micronaut.http.server)
compileOnly(libs.junit.jupiter.api)
compileOnly(mnData.micronaut.data.connection.jdbc)

api(mn.micronaut.inject)

Expand Down
66 changes: 66 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,66 @@
/*
* 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 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
@Repeatable(Sql.Sqls.class)
public @interface Sql {

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

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

/**
* 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();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.support.sql;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.io.ResourceLoader;
import io.micronaut.test.annotation.Sql;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.SQLException;

/**
* Default implementation of {@link TestSqlAnnotationHandler}.
*
* @since 4.1.0
* @author Tim Yates
*/
@Singleton
@Experimental
@Requires(env = Environment.TEST)
public final class DefaultTestSqlAnnotationHandler implements TestSqlAnnotationHandler<DataSource> {

private static final Logger LOG = LoggerFactory.getLogger(DefaultTestSqlAnnotationHandler.class);

/**
* Given a resource loader, a {@link Sql} annotation and a datasource, execute the SQL scripts in order.
*
* @param loader The resource loader
* @param sql The Sql annotation
* @param dataSource The datasource
* @throws IOException If the script cannot be read
* @throws SQLException If the script cannot be executed
*/
public void handleScript(
ResourceLoader loader,
Sql sql,
DataSource dataSource
) throws IOException, SQLException {
if (LOG.isDebugEnabled()) {
LOG.debug("Executing SQL scripts for datasource: {}", dataSource);
}
TestSqlHandlerUtils.handleScript(loader, sql, dataSource);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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.support.sql;

import io.micronaut.context.annotation.Replaces;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.io.ResourceLoader;
import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource;
import io.micronaut.test.annotation.Sql;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.sql.SQLException;

/**
* An implementation of {@link TestSqlAnnotationHandler} for {@link DelegatingDataSource}.
*
* @since 4.1.0
* @author Tim Yates
*/
@Singleton
@Experimental
@Requires(classes = DelegatingDataSource.class)
@Requires(env = Environment.TEST)
@Replaces(DefaultTestSqlAnnotationHandler.class)
public final class DelegatingTestSqlAnnotationHandler implements TestSqlAnnotationHandler<DelegatingDataSource> {

private static final Logger LOG = LoggerFactory.getLogger(DelegatingTestSqlAnnotationHandler.class);

/**
* Given a resource loader, a {@link Sql} annotation and a datasource, execute the SQL scripts in order.
*
* @param loader The resource loader
* @param sql The Sql annotation
* @param dataSource The datasource
* @throws IOException If the script cannot be read
* @throws SQLException If the script cannot be executed
*/
public void handleScript(
ResourceLoader loader,
Sql sql,
DelegatingDataSource dataSource
) throws IOException, SQLException {
if (LOG.isDebugEnabled()) {
LOG.debug("Executing SQL scripts for delegating datasource: {}", dataSource);
}
TestSqlHandlerUtils.handleScript(loader, sql, dataSource.getTargetDataSource());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.support.sql;

import io.micronaut.context.annotation.DefaultImplementation;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.io.ResourceLoader;
import io.micronaut.test.annotation.Sql;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.SQLException;

/**
* Interface for beans handing scripts for the {@link Sql} annotation.
*
* @param <T> the datasource type
*
* @since 4.1.0
* @author Tim Yates
*/
@DefaultImplementation(DefaultTestSqlAnnotationHandler.class)
@Experimental
public interface TestSqlAnnotationHandler<T extends DataSource> {

void handleScript(ResourceLoader loader, Sql sql, T dataSource) throws IOException, SQLException;
timyates marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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.support.sql;

import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.io.ResourceLoader;
import io.micronaut.test.annotation.Sql;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Optional;

/**
* Utility class that performs the SQL script execution.
*
* @since 4.1.0
* @author Tim Yates
*/
@Experimental
final class TestSqlHandlerUtils {

private static final Logger LOG = LoggerFactory.getLogger(TestSqlHandlerUtils.class);

private TestSqlHandlerUtils() {
}

static void handleScript(
ResourceLoader loader,
Sql sql,
DataSource dataSource
) throws IOException, SQLException {
for (String script : sql.value()) {
Optional<URL> resource = loader.getResource(script);
if (resource.isPresent()) {
try (
InputStream in = resource.get().openStream();
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()
) {
String scriptBody = new String(in.readAllBytes(), StandardCharsets.UTF_8);
if (LOG.isDebugEnabled()) {
LOG.debug("For connection {} executing SQL script: {}", connection, scriptBody);
}
statement.execute(scriptBody);
}
} else {
LOG.warn("Could not find SQL script: {}", script);
}
}
}
}
1 change: 1 addition & 0 deletions test-junit5/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dependencies {
testImplementation(mnData.micronaut.data.hibernate.jpa)
testImplementation(mn.snakeyaml)

testRuntimeOnly(mnLogging.logback.classic)
testRuntimeOnly(mnSql.micronaut.jdbc.tomcat)
testRuntimeOnly(mnSql.h2)
testRuntimeOnly(mnSerde.micronaut.serde.jackson)
Expand Down
Loading