spring-framework icon indicating copy to clipboard operation
spring-framework copied to clipboard

Extend default script detection in `@Sql` to account for execution phase [SPR-13322]

Open spring-projects-issues opened this issue 10 years ago • 6 comments

Robert Thaler opened SPR-13322 and commented

Status Quo

We often need to prepare data before a specific test and clean up this data after the test. Therefore we use two distinct scripts assigned to the test's ExecutionPhase.

Proposal

Appending a suffix such as _before or _after to the default script name would allow one to use default detection with different scripts for preparation and cleanup.

Deliverables

  1. Default script detection should take the ExecutionPhase into account.

Affects: 4.1 GA

spring-projects-issues avatar Aug 06 '15 02:08 spring-projects-issues

Bulk closing outdated, unresolved issues. Please, reopen if still relevant.

spring-projects-issues avatar Jan 12 '19 02:01 spring-projects-issues

Reopening due to renewed interest (see #26847).

sbrannen avatar Apr 22 '21 14:04 sbrannen

Here are some notes

As the component to amend is SqlScriptsTestExecutionListener I suggest to make some order (both in the sense of clean up and sorting)

Proposal (theoretical)

Explicit and default scripts should be executed in a predefined symmetric order

TestClassName.sql if exists
Any other @Sql-explicit script (e.g. @Sql("my-script-name.sql") on class level with default phase BEFORE
TestClassName.method.sql if exists
TestClassName.method.before.sql (note 1)
Any other @Sql-explicit script (e.g. @Sql("my-script-name.sql") on method level with default phase BEFORE
(test method execution)
Any other @Sql-explicit script (e.g. @Sql("my-script-name.sql") on method level with default phase AFTER
TestClassName.method.after.sql if exists
Any other @Sql-explicit script (e.g. @Sql("my-script-name.sql") on class level with default phase AFTER
TestClassName.after.sql

Note 1: it doesn't make sense to use both TestClass.methodName.sql and TestClass.methodName.before.sql, but both files might exist in the predefined path, so really the implementor (🤚) must take this situation into account. One for example may choose to run only the first match found.

Proposed approach

Yea, the above sounds great, but we can achieve what is written in this proposal by reducing the impact and possible regressions.

So, I think that the executionPhase argument should be propagated to detectDefaultScript. In the before phase, both TestClass.methodName.sql and TestClass.methodName.before.sql should be tested, while the after phase will test for .after.

Without changing anything else, thus without guarantees to the symmetric order proposed above in case there are multiple Sql annotations, this should work.

Any feedback from @sbrannen and/or the OP?

djechelon avatar Oct 01 '21 13:10 djechelon

Hi @djechelon,

Thanks for your input.

We will take that into account if we decide to implement this feature.

sbrannen avatar Oct 01 '21 16:10 sbrannen

Here is an implementation

I didn't run test yet, but it looks like the after test script detection works only when the @Sql annotation is present with phase set.

However, this implementation may have the least number of side effects and regressions over existing applications.

Convention over configuration is worth consideration (theoretical proposal above). Yet, I'm very interested in such a feature as I maintain a number of SQL scripts in my test classes.

Thanks for your time and consideration

djechelon avatar Oct 13 '21 12:10 djechelon

Hello @djechelon. Maybe Chained sql queries feature of DbChange JUnit extension helps you to manage with this issue?

You can use @Sql Spring Framework feature for tests where there are no requirements for default scripts and use chained sql queries of DbChange for tests where default scripts are needed. So, you can get benefits from both approaches.

As alternative way, you can also enclose tests with default scripts by classes (inner or top level):

@ExtendWith(DbChangeExtension.class)
@DbChangeOnce(sqlQueryFiles = "sql/database_init.sql")
@DbChangeOnce(sqlQueryFiles = "sql/database_destroy.sql", executionPhase = ExecutionPhase.AFTER_ALL)
@SqlExecutorGetter("defaultSqlExecutor")
public class DbChangeUsageTest {
    private DataSource dataSource;
    
    public DbChangeUsageTest() {
        dataSource = // code to create instance of DataSource 
    }
  
    public SqlExecutor defaultSqlExecutor() {
        return new DefaultSqlExecutor(dataSource);
    }
  
    @Test
    @DbChange(changeSet = InsertEmployee6Chained.class )
    @DbChange(changeSet = DeleteEmployee6Chained.class , executionPhase = DbChange.ExecutionPhase.AFTER_TEST)
    void changeSetChained() {
        /* code omitted */
    }
}

DarrMirr avatar Sep 10 '22 19:09 DarrMirr