3

I'm writing integration tests for project and I want to merge all database migration scripts into schema.sql before Spring picks it up to populate the database. For this I use a small class that searches the project for sql files and merges them into one. I've created a Suite like so:

@RunWith(Suite.class)
@Suite.SuiteClasses({MyTests.class})
public class SuiteTest {    
    @BeforeClass
    public static void setUp() throws IOException {
        RunMigrations.mergeMigrations();//this one merges all sqls into one file, called schema.sql
    }
}

Then, here's my test:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = App.class)
@ActiveProfiles(resolver = CustomActiveProfileResolver.class)
@ContextConfiguration(classes = App.class)
public class MyTests extends AbstractTransactionalJUnit4SpringContextTests {
@PostConstruct
    public void before() {
        mvc = MockMvcBuilders.webAppContextSetup(context).addFilter(springSecurityFilterChain).build();
    }    
    @Test
    @Transactional
    public void Test1(){ //do stuff }
}

But this doesn't work as I thought it would. It looks like Spring tries to run schema.sql faster than I'm creating it and fails like this:

Caused by: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [application]

If I just turn off my code that generates schema.sql and let Spring run with already created schema then all is good. But if I delete schema.sql and let my class to generate it, then it fails as described. I've tried to override run(RunNotifier notifier) method in SpringJUnit4ClassRunner and put my migration merger in there, before it calls super.run(notifier) method, but that still doesn't work. Is there a way to generate that schema.sql before Spring gets its hands on it?

P.S. I cannot use flyway for production environment. Perhaps it is possible using it just for tests?

UPD: After some experimenting, I've set this in test.yml:

spring.jpa.hibernate.ddl-auto: none

Now it loads context, performs one test which just gets Oauth2 token and fails with other tests that perform POST and GET requests because it cannot execute @sql annotations that put additional data before test methods. The database seems to be untouched, that is without any tables whatsoever.

2 Answers 2

2

You can enable flyway just for tests either by using @TestPropertySource(properties = {"spring.flyway.enabled=true"}) annotation or by creating a test Spring profile with it's own property file. The latter would look like:

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public MyTest {

with src/test/resources/application-test.yml file:

spring:
  flyway:
    enabled: true

and flyway-core as a test scoped dependency.

Do note that Flyway properties in Spring Boot were renamed in Spring Boot 2.0.

Sign up to request clarification or add additional context in comments.

1 Comment

Looks like this would require a lot of changes in my case. I have @ActiveProfiles(resolver = CustomActiveProfileResolver.class) which starts to generate errors as soon as I add flyway-test dependency to POM.
1

Perhaps someone may find this useful. I managed to solve this by using exec-maven-plugin with failsafe plugin. The test class setup remains the same, I only removed @BeforeClass annotation from Suite. Here's the POM:

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${org.apache.maven.plugins.maven-surefire-plugin-version}</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>${org.apache.maven.plugins.maven-failsafe-plugin-version}</version>
                <executions>
                    <execution>
                        <id>integration-test-for-postgres</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>verify-for-postgres</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>${org.codehaus.mojo.exec-maven-plugin-version}</version>
                <executions>
                    <execution>
                        <id>build-test-environment</id>
                        <phase>generate-test-resources</phase>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <!--This class prepares the schema.sql file which is fed to Spring to init DB before tests.-->
                    <mainClass>...GenerateTestDBSchema</mainClass>
                    <arguments>
                        <argument>...</argument><!--the migration folder-->
                        <argument>...</argument><!--The path where to put schema sql-->
                    </arguments>
                </configuration>
            </plugin>
        </plugins>
    </build>

The GenerateTestDBSchema class has main method and uses args array to accept paths where to find migrations and where to put schema.sql.

public static void main(String[] args) {
        try {
            mergeMigrations(args[0], args[1]);
        } catch (IOException e) {
            LOG.error(e.getMessage());
        }
    }

The mergeMigrations() method is straightforward: just take all files from directory, merge them and write to output path. This way Spring has it's schema.sql before context is launched and it itself decides where to run migrations. Thanks to @ActiveProfiles(resolver = CustomActiveProfileResolver.class) in integration test, spring resolves profiles and picks up application-{profileName}.yml and sets database address automatically.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.