web-dev-qa-db-fra.com

Comment recréer la base de données avant chaque test au printemps?

Mon application Spring-Boot-Mvc-Web a la configuration de base de données suivante dans le fichier application.properties:

spring.datasource.url=jdbc:h2:tcp://localhost/~/pdk
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver

c'est la seule configuration que j'ai faite. Aucune autre configuration faite par moi n'importe où. Néanmoins, le printemps et les sous-systèmes sont automatiquement recréés la base de données à chaque exécution d'application Web. La base de données est recréée, notamment lors de l'exécution du système, alors qu'elle contient des données à la fin de l'application.

Je ne comprenais pas ces valeurs par défaut et m'attendais à ce que cela convienne aux tests.

Mais lorsque j'ai commencé à exécuter des tests, j'ai constaté que la base de données n'était recréée qu'une fois. Comme les tests sont exécutés sans ordre prédéfini, cela n'a aucun sens.

Donc, la question est: comment donner un sens? C'est à dire. Comment faire pour que la base de données soit recréée avant chaque test, comme cela se produit au premier démarrage de l'application?

Mon en-tête de classe de test est la suivante:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = myapp.class)
//@WebAppConfiguration
@WebIntegrationTest
@DirtiesContext
public class WebControllersTest {

Comme vous le voyez, j'ai essayé @DirtiesContext Au niveau de la classe et cela n'a pas aidé.

MISE À JOUR

J'ai un haricot

@Service
public class DatabaseService implements InitializingBean {

qui a une méthode

@Override
    @Transactional()
    public void afterPropertiesSet() throws Exception {
        log.info("Bootstrapping data...");
        User user = createRootUser();
        if(populateDemo) {
            populateDemos();
        }
        log.info("...Bootstrapping completed");
    }

Maintenant, j'ai créé la méthode populateDemos() pour effacer toutes les données de la base de données. Malheureusement, il n’appelle pas avant chaque test malgré @DirtiesContext. Pourquoi?

37
Dims

En fait, je pense que vous voulez ceci:

@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)

http://docs.spring.io/autorepo/docs/spring-framework/4.2.6.RELEASE/javadoc-api/org/springframework/test/annotation/DirtiesContext.html

@DirtiesContext peut être utilisé comme annotation au niveau de la classe et au niveau de la méthode au sein de la même classe. Dans de tels scénarios, le champ ApplicationContext sera marqué comme étant modifié après une telle méthode annotée, ainsi que après la classe entière. Si DirtiesContext.ClassMode est défini sur AFTER_EACH_TEST_METHOD, le contexte sera marqué comme étant sale après chaque méthode de test de la classe.

65
Raphael Amoedo

Pour créer la base de données, vous devez faire ce que les autres réponses disent avec le spring.jpa.hibernate.ddl-auto=create-drop _, si votre intention est de développer la base de données à chaque test, le printemps fournit une indication très utile.

@Transactional(value=JpaConfiguration.TRANSACTION_MANAGER_NAME)
@Sql(executionPhase=ExecutionPhase.BEFORE_TEST_METHOD,scripts="classpath:/test-sql/group2.sql")
public class GroupServiceTest extends TimeoffApplicationTests {

c'est à partir de ce paquet org.springframework.test.context.jdbc.Sql; et vous pouvez exécuter une méthode avant test et une méthode après test. Pour remplir la base de données.

En ce qui concerne la création de la base de données à chaque fois, supposons que vous souhaitiez que votre test ait l'option de création/suppression. Vous pouvez configurer vos tests avec des propriétés personnalisées avec cette annotation.

@TestPropertySource(locations="classpath:application-test.properties")
public class TimeoffApplicationTests extends AbstractTransactionalJUnit4SpringContextTests{

J'espère que ça aide

9
jstuartmilne

Avec un démarrage à ressort, la base de données h2 peut être définie de manière unique pour chaque test. Il suffit de remplacer l'URL de la source de données pour chaque test.

 @SpringBootTest(properties = {"spring.config.name=myapp-test-h2","myapp.trx.datasource.url=jdbc:h2:mem:trxServiceStatus"})

Les tests peuvent être exécutés en parallèle.

Dans le test, les données peuvent être réinitialisées par

@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
5
Interlated

Si tu utilises spring.jpa.hibernate.ddl-auto=create-drop devrait suffire à créer/supprimer une base de données?

5
user2669657

Sauf si vous utilisez une sorte d'intégration Spring-Data (que je ne connais pas du tout), cela semble être une logique personnalisée que vous devrez implémenter vous-même. Spring ne connaît pas vos bases de données, ses schémas et ses tables.

En supposant que JUnit, écrivez approprié @Before et @After méthodes pour configurer et nettoyer votre base de données, ses tables et ses données. Vos tests peuvent eux-mêmes écrire les données dont ils ont besoin et éventuellement nettoyer après eux-mêmes, le cas échéant.

2

Si vous recherchez une alternative pour le @DirtiesContext, ce code ci-dessous vous aidera. J'ai utilisé du code de cette réponse .

Tout d’abord, configurez la base de données H2 sur le application.yml fichier sur votre dossier de ressources de test:

spring: 
  datasource:
    platform: h2
    url: jdbc:h2:mem:test
    driver-class-name: org.h2.Driver
    username: sa
    password:

Après cela, créez une classe appelée ResetDatabaseTestExecutionListener:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;

import javax.sql.DataSource;
import Java.sql.Connection;
import Java.sql.ResultSet;
import Java.sql.SQLException;
import Java.sql.Statement;
import Java.util.HashSet;
import Java.util.Set;

public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {

    @Autowired
    private DataSource dataSource;

    public final int getOrder() {
        return 2001;
    }

    private boolean alreadyCleared = false;

    @Override
    public void beforeTestClass(TestContext testContext) {
        testContext.getApplicationContext()
                .getAutowireCapableBeanFactory()
                .autowireBean(this);
    }

    @Override
    public void prepareTestInstance(TestContext testContext) throws Exception {

        if (!alreadyCleared) {
            cleanupDatabase();
            alreadyCleared = true;
        }
    }

    @Override
    public void afterTestClass(TestContext testContext) throws Exception {
        cleanupDatabase();
    }

    private void cleanupDatabase() throws SQLException {
        Connection c = dataSource.getConnection();
        Statement s = c.createStatement();

        // Disable FK
        s.execute("SET REFERENTIAL_INTEGRITY FALSE");

        // Find all tables and truncate them
        Set<String> tables = new HashSet<>();
        ResultSet rs = s.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES  where TABLE_SCHEMA='PUBLIC'");
        while (rs.next()) {
            tables.add(rs.getString(1));
        }
        rs.close();
        for (String table : tables) {
            s.executeUpdate("TRUNCATE TABLE " + table);
        }

        // Idem for sequences
        Set<String> sequences = new HashSet<>();
        rs = s.executeQuery("SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='PUBLIC'");
        while (rs.next()) {
            sequences.add(rs.getString(1));
        }
        rs.close();
        for (String seq : sequences) {
            s.executeUpdate("ALTER SEQUENCE " + seq + " RESTART WITH 1");
        }

        // Enable FK
        s.execute("SET REFERENTIAL_INTEGRITY TRUE");
        s.close();
        c.close();
    }
}

Le code ci-dessus réinitialise la base de données (tables tronquées, réinitialisation de séquences, etc.) et est prêt à fonctionner avec la base de données H2. Si vous utilisez une autre base de données mémoire (telle que HsqlDB), vous devez apporter les modifications nécessaires aux requêtes SQL pour obtenir le même résultat.

Après cela, allez dans votre classe de test et ajoutez le @TestExecutionListeners annotation, comme:

@TestExecutionListeners(mergeMode =
        TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
        listeners = {ResetDatabaseTestExecutionListener.class}
)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CreateOrderIT {

Cela devrait marcher.

Si vous ne voyez aucune différence de performance entre cette approche et @DirtiesContext, probablement vous utilisez @MockBean à l'intérieur de vos tests, qu'est-ce qui marque le contexte comme sale et recharger automatiquement le contexte Spring?.

2
Dherik