Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ApplicationContextAssert to support AssertJ's soft assertions #40134

Open
filipowm opened this issue Mar 29, 2024 · 1 comment
Open

Update ApplicationContextAssert to support AssertJ's soft assertions #40134

filipowm opened this issue Mar 29, 2024 · 1 comment
Labels
type: enhancement A general enhancement

Comments

@filipowm
Copy link

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(...);
});
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 29, 2024
@philwebb philwebb added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Apr 4, 2024
@philwebb philwebb added this to the General Backlog milestone Apr 4, 2024
@philwebb
Copy link
Member

philwebb commented Apr 4, 2024

It would be nice to contribute some ideas to assertj/assertj#2817. If we has a SoftAssertProvider we might be able to do:

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

@wilkinsona wilkinsona changed the title ApplicationContextAssert does not work with Assertj SoftAssertions Update ApplicationContextAssert to support AssertJ's soft assertions Apr 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants