Bean Overriding in Tests

Bean Overriding in Tests refers to the ability to override specific beans in the Context for a test class, by annotating one or more fields in said test class.

This is intended as a less risky alternative to the practice of registering a bean via @Bean with the StandardBeanFactory setAllowBeanDefinitionOverriding set to true.

The Infra Testing Framework provides two sets of annotations presented below. One relies purely on Infra, while the second set relies on the Mockito third party library.

@TestBean

@TestBean is used on a test class field to override a specific bean with an instance provided by a conventionally named static method.

By default, the bean name and the associated static method name are derived from the annotated field’s name, but the annotation allows for specific values to be provided.

The @TestBean annotation uses the REPLACE_DEFINITION strategy for test bean overriding.

The following example shows how to fully configure the @TestBean annotation, with explicit values equivalent to the default:

  • Java

class OverrideBeanTests {
  @TestBean(name = "service", methodName = "serviceTestOverride")  (1)
  private CustomService service;

  // test case body...

  private static CustomService serviceTestOverride() { (2)
    return new MyFakeCustomService();
  }
}
1 Mark a field for bean overriding in this test class
2 The result of this static method will be used as the instance and injected into the field
The method to invoke is searched in the test class and any enclosing class it might have, as well as its hierarchy. This typically allows nested test class to rely on the method to use in the root test class.

@MockitoBean and @MockitoSpyBean

@MockitoBean and @MockitoSpyBean are used on a test class field to override a bean with a mocking and spying instance, respectively. In the later case, the original bean definition is not replaced but instead an early instance is captured and wrapped by the spy.

By default, the name of the bean to override is derived from the annotated field’s name, but both annotations allows for a specific name to be provided. Each annotation also defines Mockito-specific attributes to fine-tune the mocking details.

The @MockitoBean annotation uses the CREATE_OR_REPLACE_DEFINITION strategy for test bean overriding.

The @MockitoSpyBean annotation uses the WRAP_EARLY_BEAN strategy and the original instance is wrapped in a Mockito spy.

The following example shows how to configure the bean name for both @MockitoBean and @MockitoSpyBean annotations:

  • Java

class OverrideBeanTests {
  @MockitoBean(name = "service1")  (1)
  private CustomService mockService;

  @MockitoSpyBean(name = "service2") (2)
  private CustomService spyService; (3)

  // test case body...
}
1 Mark mockService as a Mockito mock override of bean service1 in this test class
2 Mark spyService as a Mockito spy override of bean service2 in this test class
3 Both fields will be injected with the Mockito values (the mock and the spy respectively)

Extending bean override with a custom annotation

The three annotations introduced above build upon the @BeanOverride meta-annotation and associated infrastructure, which allows to define custom bean overriding variants.

To create an extension, the following is needed:

  • An annotation meta-annotated with @BeanOverride that defines the BeanOverrideProcessor to use.

  • The BeanOverrideProcessor implementation itself.

  • One or more concrete OverrideMetadata implementations provided by the processor.

The Infra TestContext Framework includes infrastructure classes that support bean overriding: a BeanFactoryPostProcessor, a TestExecutionListener and a ContextCustomizerFactory. The later two are automatically registered via the Infra TestContext Framework infra.factories file, and are responsible for setting up the rest of the infrastructure.

The test classes are parsed looking for any field meta-annotated with @BeanOverride, instantiating the relevant BeanOverrideProcessor in order to register an OverrideMetadata.

Then the BeanOverrideBeanFactoryPostProcessor will use that information to alter the context, registering and replacing bean definitions as defined by each metadata BeanOverrideStrategy:

  • REPLACE_DEFINITION: replaces the bean definition. If it is not present in the context, an exception is thrown.

  • CREATE_OR_REPLACE_DEFINITION: replaces the bean definition if the bean definition does not exist, or create one if it is not.

  • WRAP_BEAN: get the original instance early so that it can be wrapped.

The Bean Overriding infrastructure does not include any bean resolution step, unlike an @Autowired-annotated field for instance. As such, the name of the bean to override must be somehow provided to or computed by the BeanOverrideProcessor. Typically, the user provides the name one way or the other.