spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.58k stars 40.55k forks source link

Introduce additional @Nested tests in test suite #23929

Open sbrannen opened 3 years ago

sbrannen commented 3 years ago

Since #12470 has been resolved and due to the general support for automatically inheriting test configuration from enclosing classes for JUnit Jupiter @Nested tests in Spring Framework 5.3, it would be good to have more @Nested tests in the Spring Boot test suite in order to verify the expected behavior for various Spring Boot Test annotations when using @Nested test classes.

Aside from tests that verify the default, inherited behavior, it would also be good to have a few dedicated tests that verify support for @NestedTestConfiguration(OVERRIDE) semantics for Spring Boot Test annotations.

For Spring Framework, I added several such test classes in the org.springframework.test.context.junit.jupiter.nested package which may serve as inspiration.

fbiville commented 3 years ago

I initially opened an issue about nested test support against Spring Data Commons. This was the wrong place since this is not a Spring Data issue but rather possibly a Spring Boot or Framework one.

I could not make nested tests work with Spring Data JPA 2.4.1 (Spring Boot 2.4.0). I also tried with the latest Spring Data Neo4j (in a private repository) with the same result.

You can find the sample JPA repository here with reproduction instructions: https://github.com/fbiville/spring-data-jpa-nested.

wilkinsona commented 3 years ago

Thanks, @fbiville. I think this is a Spring Framework bug. As far as I can tell the @DynamicPropertySource isn't being picked up by ActorTests and MovieTests. As a result, the spring.datasource.url property isn't set so the DataSource can't be created.

wilkinsona commented 3 years ago

The tests pass and share a single context if the use of @DynamicPropertySource is replaced with an equivalent ApplicationContextIntializer:

package com.example.demo;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace.NONE;
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.NestedTestConfiguration;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import com.example.demo.DemoApplicationTests.PropertiesInitializer;
import com.example.demo.actors.Actor;
import com.example.demo.actors.ActorRepository;
import com.example.demo.movies.Movie;
import com.example.demo.movies.MovieRepository;

@Testcontainers
@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE)
@NestedTestConfiguration(INHERIT)
@ContextConfiguration(initializers = PropertiesInitializer.class)
class DemoApplicationTests {

    @Autowired
    EntityManager entityManager;

    @Container
    private static final MySQLContainer<?> mysqlContainer = new MySQLContainer<>("mysql:5.7");

    @Nested
    class ActorTests {

        @Autowired
        ActorRepository actorRepository;

        @BeforeEach
        void prepareActors() {
            Query query = entityManager.createQuery("DELETE FROM Actor");
            query.executeUpdate();
            entityManager.persist(actor("Emil Eifrem"));
        }

        @Test
        void loads_actors() {
            List<Actor> actors = actorRepository.findAll();

            assertThat(actors).hasSize(1);
            assertThat(actors.iterator().next().getName()).isEqualTo("Emil Eifrem");
        }

        private Actor actor(String name) {
            Actor actor = new Actor();
            actor.setName(name);
            return actor;
        }
    }

    @Nested
    class MovieTests {

        @Autowired
        MovieRepository movieRepository;

        @BeforeEach
        void prepareMovies() {
            Query query = entityManager.createQuery("DELETE FROM Movie");
            query.executeUpdate();
            entityManager.persist(movie("The Matrix"));
        }

        @Test
        void loads_movies() {
            List<Movie> movies = movieRepository.findAll();

            assertThat(movies).hasSize(1);
            assertThat(movies.iterator().next().getTitle()).isEqualTo("The Matrix");
        }

        private Movie movie(String title) {
            Movie movie = new Movie();
            movie.setTitle(title);
            return movie;
        }
    }

    static class PropertiesInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            TestPropertyValues.of("spring.datasource.url=" + mysqlContainer.getJdbcUrl(),
                    "spring.datasource.username=" + mysqlContainer.getUsername(),
                    "spring.datasource.password=" + mysqlContainer.getPassword(), 
                    "spring.jpa.hibernate.ddl-auto=create").applyTo(applicationContext);;
        }

    }

}
wilkinsona commented 3 years ago

@fbiville Can you please open a Spring Framework issue and comment here with a link to it so that @sbrannen can take a look?

fbiville commented 3 years ago

Issue created: https://github.com/spring-projects/spring-framework/issues/26091. Sorry for reporting that issue against the wrong project twice 😅