From f0aff3aa3c5ef7c51549823b5b8afba9a159f8db Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 13 Mar 2026 12:02:32 +0000 Subject: [PATCH] Polish "Add failure analysis for missing auto-configured MailSender" See gh-49582 Signed-off-by: Andy Wilkinson --- .../NoSuchMailSenderBeanFailureAnalyzer.java | 49 ++++++++--- ...uchMailSenderBeanFailureAnalyzerTests.java | 82 ++++++++++--------- 2 files changed, 82 insertions(+), 49 deletions(-) diff --git a/module/spring-boot-mail/src/main/java/org/springframework/boot/mail/autoconfigure/NoSuchMailSenderBeanFailureAnalyzer.java b/module/spring-boot-mail/src/main/java/org/springframework/boot/mail/autoconfigure/NoSuchMailSenderBeanFailureAnalyzer.java index 985e9daf8db..7997fc332c8 100644 --- a/module/spring-boot-mail/src/main/java/org/springframework/boot/mail/autoconfigure/NoSuchMailSenderBeanFailureAnalyzer.java +++ b/module/spring-boot-mail/src/main/java/org/springframework/boot/mail/autoconfigure/NoSuchMailSenderBeanFailureAnalyzer.java @@ -16,18 +16,27 @@ package org.springframework.boot.mail.autoconfigure; +import java.util.Map; + import org.jspecify.annotations.Nullable; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.mail.autoconfigure.MailSenderAutoConfiguration.MailSenderCondition; import org.springframework.core.Ordered; -import org.springframework.core.env.Environment; import org.springframework.mail.MailSender; /** * An {@link AbstractFailureAnalyzer} that improves missing {@link MailSender} guidance - * when mail auto-configuration is present but not activated. + * when {@link MailSenderAutoConfiguration} is present but did not match. + * + * @author MJY (answndud) + * @author Andy Wilkinson */ class NoSuchMailSenderBeanFailureAnalyzer extends AbstractFailureAnalyzer implements Ordered { @@ -36,18 +45,22 @@ class NoSuchMailSenderBeanFailureAnalyzer extends AbstractFailureAnalyzer conditionAndOutcomesBySource = conditionEvaluationReport + .getConditionAndOutcomesBySource(); + ConditionAndOutcomes conditionAndOutcomes = conditionAndOutcomesBySource + .get(MailSenderAutoConfiguration.class.getName()); + if (conditionAndOutcomes != null) { + return conditionAndOutcomes.stream() + .filter((candidate) -> candidate.getCondition() instanceof MailSenderCondition) + .findFirst() + .orElse(null); + } + } + return null; + } + private boolean isMissingMailSenderBean(NoSuchBeanDefinitionException cause) { Class beanType = cause.getBeanType(); if (beanType == null && cause.getResolvableType() != null) { @@ -64,11 +94,6 @@ class NoSuchMailSenderBeanFailureAnalyzer extends AbstractFailureAnalyzer { + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + FailureAnalysis analysis = new NoSuchMailSenderBeanFailureAnalyzer(beanFactory) + .analyze(new Exception()); + assertThat(analysis).isNull(); + }); } @Test void analyzeWhenNoSuchBeanDefinitionExceptionForDifferentTypeShouldReturnNull() { - assertThat( - new NoSuchMailSenderBeanFailureAnalyzer(null).analyze(new NoSuchBeanDefinitionException(String.class))) - .isNull(); - } - - @Test - void analyzeWhenMailHostPropertyIsConfiguredShouldReturnNull() { - Environment environment = new MockEnvironment().withProperty("spring.mail.host", "smtp.example.org"); - assertThat(new NoSuchMailSenderBeanFailureAnalyzer(environment) - .analyze(new NoSuchBeanDefinitionException(MailSender.class))).isNull(); - } - - @Test - void analyzeWhenMailJndiNamePropertyIsConfiguredShouldReturnNull() { - Environment environment = new MockEnvironment().withProperty("spring.mail.jndi-name", "mail/Session"); - assertThat(new NoSuchMailSenderBeanFailureAnalyzer(environment) - .analyze(new NoSuchBeanDefinitionException(MailSender.class))).isNull(); + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class)) + .run((context) -> { + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + assertThatException().isThrownBy(() -> context.getBean(String.class)).satisfies((ex) -> { + FailureAnalysis analysis = new NoSuchMailSenderBeanFailureAnalyzer(beanFactory).analyze(ex); + assertThat(analysis).isNull(); + }); + }); } @Test - void analyzeWhenMailSenderBeanIsMissingAndNoMailPropertiesAreConfiguredShouldProvideGuidance() { - FailureAnalysis analysis = new NoSuchMailSenderBeanFailureAnalyzer(new MockEnvironment()) - .analyze(new NoSuchBeanDefinitionException(MailSender.class)); - assertThat(analysis).isNotNull(); - assertThat(analysis.getDescription()) - .contains("A MailSender bean could not be found") - .contains("spring.mail.host") - .contains("spring.mail.jndi-name"); - assertThat(analysis.getAction()) - .contains("spring.mail.host") - .contains("spring.mail.jndi-name") - .contains("MailSender bean"); + void analyzeWithoutMailSenderAutoConfigurationShouldReturnNull() { + new ApplicationContextRunner().run((context) -> { + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + assertThatException().isThrownBy(() -> context.getBean(MailSender.class)).satisfies((ex) -> { + FailureAnalysis analysis = new NoSuchMailSenderBeanFailureAnalyzer(beanFactory).analyze(ex); + assertThat(analysis).isNull(); + }); + }); } @Test - void analyzeWhenJavaMailSenderBeanIsMissingAndNoMailPropertiesAreConfiguredShouldProvideGuidance() { - assertThat(new NoSuchMailSenderBeanFailureAnalyzer(new MockEnvironment()) - .analyze(new NoSuchBeanDefinitionException(JavaMailSender.class))).isNotNull(); + void analyzeWhenMailSenderBeanIsMissingAndMailSenderConditionDidNotMatchShouldProvideGuidance() { + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(MailSenderAutoConfiguration.class)) + .run((context) -> { + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + assertThatException().isThrownBy(() -> context.getBean(MailSender.class)).satisfies((ex) -> { + FailureAnalysis analysis = new NoSuchMailSenderBeanFailureAnalyzer(beanFactory).analyze(ex); + assertThat(analysis).isNotNull(); + assertThat(analysis.getDescription()).contains("A MailSender bean could not be found") + .contains("spring.mail.host") + .contains("spring.mail.jndi-name"); + assertThat(analysis.getAction()).contains("spring.mail.host") + .contains("spring.mail.jndi-name") + .contains("MailSender bean"); + }); + }); } }