Тестирование ленивой загрузки в спящем режиме (java весна с транзакциями) - PullRequest
0 голосов
/ 10 июля 2020

У меня следующая настройка: два объекта (родительский, дочерний), дочерние элементы должны быть лениво загружены в родительский элемент, который отлично работает, расширяя JpaRepository с помощью настраиваемого репозитория ParentCustomRepository и вызывая Hibernate.initialize. Следующий код работает должным образом.

@Entity(name = "children")
public class Child {

    @GeneratedValue
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.EAGER, optional = false)
    @JoinColumn(name = "parent_id", nullable = false)
    private Parent parent;

    @Column
    private String name;

    // Getter and Setter...
}
@Entity(name = "parents")
public class Parent {

    @GeneratedValue
    @Id
    private Long id;

    @OneToMany(
            mappedBy = "parent",
            fetch = FetchType.LAZY
    )
    private List<Child> children;

    @Column
    private String name;

    // Getter and Setter...
}
public interface ChildRepository extends JpaRepository<Child, Long> {}
public interface ParentRepository extends JpaRepository<Parent, Long>, ParentCustomRepository {}
public interface ParentCustomRepository {
    List<Parent> findAllWithChildren();
}
@Transactional
@Component
public class ParentCustomRepositoryImpl implements ParentCustomRepository {

    private final ParentRepository parentRepository;

    @Lazy
    @Autowired
    public ParentCustomRepositoryImpl(ParentRepository parentRepository) {
        this.parentRepository = parentRepository;
    }

    @Override
    public List<Parent> findAllWithChildren() {
        final List<Parent> parents = this.parentRepository.findAll();
        parents.forEach(parent -> {
            Hibernate.initialize(parent.getChildren());
        });
        return parents;
    }
}

Пока все хорошо. Однако я не могу заставить тесты работать приемлемым способом. Я хочу иметь тесты, которые независимы друг от друга таким образом, чтобы не было необходимости в fre sh контексте для каждого теста. Вот простой тестовый класс, который показывает, что я имею в виду.

@SpringBootTest
// @Transactional // uncomment this line to enable auto rollback after each test.
@ActiveProfiles("test")
public class ExampleTest {

    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    @Autowired
    private ParentRepository parentRepository;

    @Autowired
    private ChildRepository childRepository;

    @BeforeEach
    public void setUp() {
        if (parentRepository.count() > 0) {
            LOG.info("Already Set Up");
            return;
        }

        LOG.info("Set Up");

        Parent christian = new Parent();
        christian.setName("Christian");

        christian = parentRepository.save(christian);

        Child alex = new Child();
        alex.setName("Alex");
        alex.setParent(christian);

        alex = childRepository.save(alex);

        Child daniela = new Child();
        daniela.setName("Daniela");
        daniela.setParent(christian);

        daniela = childRepository.save(daniela);

        christian.setChildren(Arrays.asList(alex, daniela));

        // commit transaction when using @Transactional 
    }


    @Test
    public void givenStoredParent_whenGetAllChildren_thenCorrectSize() {
        checkSize();
    }

    @Test
    public void givenStoredParent_whenGetAllChildren_thenCorrectSize_secondTime() {
        checkSize();
    }

    @Test
    public void givenStoredParent_whenAddChild_thenUpdatedSize() {
        addChild();
    }

    @Test
    public void givenStoredParent_whenAddChild_thenUpdatedSize_secondTime() {
        addChild();
    }

    public void addChild() {
        assertThat(childRepository.count()).isEqualTo(2);

        final List<Parent> parents = parentRepository.findAllWithChildren();
        assertThat(parents.size()).isEqualTo(1);

        Parent christian = parents.get(0);
        assertThat(christian.getChildren().size()).isEqualTo(2);

        Child peter = new Child();
        peter.setName("Peter");
        peter.setParent(christian);

        childRepository.saveAndFlush(peter);

        assertThat(childRepository.count()).isEqualTo(3);

        assertThat(childRepository.findAll()).allMatch(child -> {
            return child.getParent().getName().equals("Christian");
        });

        final List<Parent> updatedParents = parentRepository.findAllWithChildren();
        assertThat(updatedParents.size()).isEqualTo(1);

        christian = updatedParents.get(0);
        assertThat(christian.getChildren().size()).isEqualTo(3);
    }

    public void checkSize() {
        final List<Parent> parents = parentRepository.findAllWithChildren();
        assertThat(parents.size()).isEqualTo(1);

        Parent christian = parents.get(0);
        assertThat(christian.getChildren().size()).isEqualTo(2);
    }
}
  • Если я запускаю эти тесты без аннотации @Transactional, каждая операция save сохраняется изменения в базе данных, что приводит к другим результатам в остальных тестах.
    • Я знаю, что могу просто очищать базу данных после каждого теста с помощью метода tearDown, помеченного @AfterEach. Это приведет к чистому началу каждого теста и кажется вполне подходящим для этого примера. Однако приложение, которое я разрабатываю для своей компании, зависит от гораздо большей инициализированной базы данных, что потребует довольно много времени для сотен тестов. (Это также тот случай, почему я не хочу использовать @DirtiesContext где-нибудь в тестах)
  • Если я запускаю эти тесты с аннотация @Transactional, откат выполняется после каждого теста. Это именно то поведение, которое я ищу. Как видно из результатов теста, некоторые тесты не проходят. Это связано с тем, что childRepository.save(peter) не будет сохранять добавленный дочерний элемент и, следовательно, вызов `parentRepository.findAllWithChildren () вернет только дочерние элементы, которые находятся в базе данных с самого начала.
    • Я знаю, что могу просто сохранить транзакцию через фиксацию, но тогда у меня больше не будет независимых тестов.
    • Использование @Transactional(isolation = Isolation.READ_UNCOMMITTED) в тестовом классе не работает. Поскольку я не хочу использовать этот уровень изоляции в реальном программном коде, я не проводил дальнейших исследований в этом направлении.

Я предполагаю, что мне нужно a commit, прежде чем я смог получить доступ к обновленным отношениям между родителем и его дочерними элементами. Коммит также очищает все точки сохранения, и, следовательно, я не могу выполнить откат даже с настраиваемым управлением транзакциями. из вас уже сталкивались с подобными проблемами и могли бы предложить решение или объяснение. Есть ли какие-то передовые методы, о которых я не знаю?

Alex

1 Ответ

0 голосов
/ 10 июля 2020

Вы можете просто создать метод @AfterEach, в котором вы восстанавливаете исходную базу данных (например, вы удаляете добавленные строки, если они существуют, и т. Д. c.).

...