javaspringspring-bootspring-boot-test

Spring Boot `ApplicationContextRunner` loads `Lazy` defined beans


In the following configuration bean example,

@Configuration
public class LazyConfig
{
    @Bean
    @Lazy
    public SomeBean someBean(final DependencyBean dependencyBean)
    {
        return new SomeBean(dependencyBean);
    }
}

I expected the following test using ApplicationContextRunner should be working, but it fails:

class LazyConfigTest
{
  ApplicationContextRunner runner = new ApplicationContextRunner()
    .withUserConfiguration(LazyConfig.class);

  @Test
  void testNoLazyIsLoaded()
  {
    runner.run(context -> {
      org.assertj.core.api.Assertions.assertThat(context).getBeanNames(SomeBean.class).isEmpty();
            // > Expecting empty but was: ["someBean"]
      org.assertj.core.api.Assertions.assertThat(context).doesNotHaveBean(SomeBean.class);
            // > Expecting not to have, but found: <["someBean"]>
    });
  }
}

But then trying to get the bean itself fails for depedant,

context.getBean(SomeBean.class);
// > Error creating bean with name 'someBean' defined in org.lucasvc.lazy.config.LazyConfig: Unsatisfied dependency expressed through method 'someBean' parameter 0: No qualifying bean of type 'org.lucasvc.lazy.DependencyBean' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

It is expected that there is such "beans" in the context, even annotated method with Lazy? How should be the best way to test the Lazy behavior?


Solution

  • You have to consider how @Lazy beans work in Spring. If I have a class LazyBeanA that is marked as @Lazy, and I declare a class NotLazyB that autowires LazyBeanA does that negate LazyBeanA's laziness? No, it doesn't. But how does it construct NotLazyB if it's "eager" if not all properties can be satisfied? It can't satisfy the LazyBeanA autowire because it's lazy. Well, Spring gets around that by instantiating a proxy that sits in front of LazyBeanA. LazyBeanA isn't constructed, but the Proxy is. The proxy is a "stunt" bean. It's stands in for LazyBeanA until you invoke a method on it. At that point it constructs LazyBeanA, populates it's dependencies, and invokes the method.

    So there is something registered under that bean's class/id. It's just the stunt bean.