spring-projects / spring-projects/spring-boot

Update ApplicationContextAssert to support AssertJ's soft assertions

Open
#40,134 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

type: enhancement
Dominant language
Java
Stars
81.5k
Forks
42.7k
Avg merge
2d 4h
Merged PRs (30d)
65

Description

Problem

I would like to be able to use AssertJ soft assertions on ApplicationContext, the same way as it works on regular assertions like Assertions.assertThat(context).doesNotHaveBean(...).hasSingleBean(...). However, it seems to be not possible with current implementation of both ApplicationContextAssert and SoftAssertions (proxying there in particular).

My setup:

  • Spring Boot 3.2.2
  • Spring Boot Test 3.2.2
  • AssertJ 3.25.3
  • Java 21
How to get there

To give you examples of what I would like to achieve and my path:

private final ApplicationContextRunner runner = new ApplicationContextRunner().withUserConfiguration(MyConfiguration.class);

@Test
void this_one_wraps_context_into_object_assert() {
    runner.withPropertyValues("myapp.something.enabled=false")
          .run(context ->
                       assertSoftly(softly -> {
                           // softly.assertThat(context).doesNotHaveBean(MyBean.class); this is not possible because of how AssertJ creates proxies
                              softly.assertThat(context)... // here ObjectAssert is created instead of ApplicationContextAssert
                              softly.assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
                                    .isThrownBy(() -> context.getBean(MyBean.class)); // this is the only working solution now
                       })
           );
}

I prefer syntax of doesNotHaveBean instead of checking if exception was thrown, because it's way easier to read, more fluent (I can chain multiple assertions) and checks for startup failures and provides resonable error message in case assertion fails (I can workaround message with as from assertj).

I took another attempt to create soft assertion proxy manually, so that I should be able to use fluent assertions with ApplicationContextAssert:

@Test
void this_one_throws_exception() {
    runner.withPropertyValues("myapp.something.enabled=false")
          .run(context ->
                       assertSoftly(softly -> {
                           softly.proxy(ApplicationContextAssert.class, ApplicationContext.class, context)
                                    .doesNotHaveBean(MyBean.class)
                                    .hasSingleBean(AnotherBean.class);
                       })
           );
}

but it fails with

java.lang.NoSuchMethodException: org.springframework.boot.test.context.assertj.ApplicationContextAssert$ByteBuddy$rqYvGSgX.<init>(org.springframework.context.ApplicationContext)

because AssertJ proxying requires single argument constructor (actual value), while ApplicationContextAssert has following signature:

ApplicationContextAssert(C applicationContext, Throwable startupFailure)

The problem here is Throwable passed as second argument to constructor, what makes AssertJ soft assertions proxying not work. I was looking at way to use ApplicationContextAssertProvider, but with no success. Workaround would be to create custom class with single constructor extending ApplicationContextAssert, but it's not possible due to package-private scope of the constructor.

Solution

Be able to use AssertJ soft assertions with ApplicationContextAssert:

SoftAssertions.assertSoftly(softly -> {
   softly.assertThat(context).doesNotHaveBean(...).hasSingleBean(...);
   softly.assertThat(context.getEnvironment()).hasFieldOrProperty(...);
});

Acceptable temporary workaround is to create soft assertion proxy manually:

SoftAssertions.assertSoftly(softly -> {
   softly.proxy(ApplicationContextAssert.class, ApplicationContext.class, context)
            .doesNotHaveBean(...)
            .hasSingleBean(...);
   softly.assertThat(context.getEnvironment()).hasFieldOrProperty(...);
});

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by examining ApplicationContextAssert, ApplicationContextAssertProvider, and AssertJ's SoftAssertions proxy behavior, then reproduce the supplied ApplicationContextRunner examples. The work is done when soft assertions can use ApplicationContextAssert fluently, including chained doesNotHaveBean and hasSingleBean calls, while the existing workaround remains supported.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring-boot
Domain
testing
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
63/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.