Class AnnotatedTestFieldSetup<A extends Annotation,T>

java.lang.Object
ru.vyarus.dropwizard.guice.test.jupiter.env.field.AnnotatedTestFieldSetup<A,T>
Type Parameters:
A - annotation type
T - required field type for auto validations (could be Object)
All Implemented Interfaces:
TestExecutionListener, TestEnvironmentSetup
Direct Known Subclasses:
LogFieldsSupport, MockFieldsSupport, RestStubFieldsSupport, SpyFieldsSupport, StubFieldsSupport, TrackerFieldsSupport, WebClientFieldsSupport, WebResourceClientFieldsSupport

public abstract class AnnotatedTestFieldSetup<A extends Annotation,T> extends Object implements TestEnvironmentSetup, TestExecutionListener
Base class for annotated test field extensions. The main purpose is to hide all complexity of junit test instance management (including potential nested tests support), simplifying actual extension implementation.

Encapsulates:

  • fields detection and processing
  • declaration validations
  • nested tests support (avoid duplicate fields initialization)
  • manual user values support
  • injected field lifecycle support (if required to reset an object before each test)

The overall process is: find all annotated fields, request supporting object initialization (field value might be provided by user), request a field value object to inject into test (not calling if already initialized).

Each field is represented by a self-contained object (AnnotatedField), which could hold an additional managing object to avoid external state requirement (all related objects already presented in field instance - no need for an external state).

It is recommended to encapsulate the main logic into a separate hook (a hook object would perform required application modifications). Setup object should only provide the required configuration to this hook. This should greatly simplify the extension and make it more testable.

All methods are protected to simplify overriding logic if required.

Implementation could be registered as usual TestEnvironmentSetup object (hook and listener would be registered automatically).

Since:
10.02.2025
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    protected boolean
    True if guicey extension start application per test class (for all methods).
    protected static final String
    Indicates injected field value (or pre-initialized value presence).
    protected static final String
    Indicates user-provided value (field was pre-initialized by user).
    protected List<AnnotatedField<A,T>>
    Resolved annotated fields.
    protected Class<?>
    Test class.
    protected org.junit.jupiter.api.extension.ExtensionContext
    Junit context, used for fields search.
    protected String
    Human-readable (if @DisplayName or spock used) class or method name.
  • Constructor Summary

    Constructors
    Constructor
    Description
    AnnotatedTestFieldSetup(Class<A> fieldAnnotation, Class<T> fieldType, String storageKey)
    On extending, use a default constructor and specify required parameters manually.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    Called after each test method execution.
    protected abstract void
    afterTest(EventContext context, AnnotatedField<A,T> field, T value)
    Called after each test to post-process field value (if required).
    void
    IMPORTANT: this method MIGHT NOT BE CALLED at all in case if extension is registered under non-static field (and so application created before each method).
    void
    Called before each test method execution.
    protected abstract void
    beforeTest(EventContext context, AnnotatedField<A,T> field, T value)
    Called before each test to pre-process field value (if required).
    protected abstract void
    Called after application startup and before field value injection.
    protected void
    failIfInstanceFieldInitialized(AnnotatedField<A,T> field, org.junit.jupiter.api.extension.TestInstances testInstances)
    When guicey extension starts in beforeAll - it can't see instance fields (by default) and so can't check if use provide any value.
    protected abstract void
    fieldDetected(org.junit.jupiter.api.extension.ExtensionContext context, AnnotatedField<A,T> field)
    Validate resolved field, if required.
    protected org.junit.jupiter.api.extension.ExtensionContext
    getClassContext(org.junit.jupiter.api.extension.ExtensionContext context)
    Class context is the same for all test methods, and so it is suitable for storing something that must survive between test methods.
    protected String
     
    protected List<AnnotatedField<A,T>>
    getOwnFields(org.junit.jupiter.api.extension.ExtensionContext context)
     
    protected List<AnnotatedField<A,T>>
    getParentFields(org.junit.jupiter.api.extension.ExtensionContext context)
    This is important for the nested tests - each nested test may have its own set of fields (if guicey extension created per test method).
    protected org.junit.jupiter.api.extension.ExtensionContext.Store
    getStore(org.junit.jupiter.api.extension.ExtensionContext context)
    Gets test-specific extension storage.
    protected abstract <K> void
    initializeField(AnnotatedField<A,T> field, T userValue)
    Configure application for a field (user value might be provided).
    protected abstract T
    Get test field value (would be immediately injected into the test field).
    protected void
    injectValues(EventContext context, List<AnnotatedField<A,T>> fields, org.junit.jupiter.api.extension.TestInstances testInstances)
    Inject field values into test instance (under beforeEach).
    protected boolean
    isInstanceBinding(com.google.inject.Binding<?> binding)
    Note: covers only the simplest cases (just for a basic validations).
    protected List<AnnotatedField<A,T>>
    lookupFields(org.junit.jupiter.api.extension.ExtensionContext context, jakarta.inject.Provider<List<AnnotatedField<A,T>>> fieldsProvider)
    Resolve test own fields or use already resolved fields set (for example, when guicey extension created for each method we can search and validate fields just once).
    protected abstract void
    Called to register additional guicey hooks, if required.
    protected abstract void
    report(EventContext context, List<AnnotatedField<A,T>> fields)
    Called when debug is enabled on guicey extension to report registered fields.
    setup(TestExtension extension)
    Called before test application startup under junit "before all" phase or "before each" (depends on extension registration).
    void
    Called when dropwizard (or guicey) application started.
    void
    Called before dropwizard (or guicey) application starting.
    void
    Called when dropwizard (or guicey) application stopped.
    protected void
    When guicey extension created in beforeAll, same extension would be used for nested tests, which means that, if nested test declares any annotated fields, they can't be injected into already started guice context and so usage error must be reported.
    protected void
    valueLifecycle(EventContext context, List<AnnotatedField<A,T>> fields, org.junit.jupiter.api.extension.TestInstances testInstances, boolean before)
    Called in beforeEach/afterEach to apply automatic lifecycle for field objects.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

    Methods inherited from interface ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener

    afterAll, stopping
  • Field Details

    • FIELD_MANUAL

      protected static final String FIELD_MANUAL
      Indicates user-provided value (field was pre-initialized by user).
      See Also:
    • FIELD_INJECTED

      protected static final String FIELD_INJECTED
      Indicates injected field value (or pre-initialized value presence).
      See Also:
    • appPerClass

      protected boolean appPerClass
      True if guicey extension start application per test class (for all methods).
    • regTestClass

      protected Class<?> regTestClass
      Test class.
    • setupContextName

      protected String setupContextName
      Human-readable (if @DisplayName or spock used) class or method name.
    • fields

      protected List<AnnotatedField<A extends Annotation,T>> fields
      Resolved annotated fields.
    • setupContext

      protected org.junit.jupiter.api.extension.ExtensionContext setupContext
      Junit context, used for fields search. Required for reporting (because the report would be generated on the different phase).
  • Constructor Details

    • AnnotatedTestFieldSetup

      public AnnotatedTestFieldSetup(Class<A> fieldAnnotation, Class<T> fieldType, String storageKey)
      On extending, use a default constructor and specify required parameters manually.
      Parameters:
      fieldAnnotation - field annotation class
      fieldType - required fields type (could be Object)
      storageKey - key used to store a fields list in junit context (must be unique for extension)
  • Method Details

    • setup

      public Object setup(TestExtension extension)
      Description copied from interface: TestEnvironmentSetup
      Called before test application startup under junit "before all" phase or "before each" (depends on extension registration). Assumed to be used for starting additional test objects (like embedded database) and application configuration (configuration overrides).Provided object allow you to provide direct configuration overrides (e.g. to override database credentials).

      For simplicity, any non closable returned object simply ignored. This was done to simplify lambas usage: TestEnvironmentSetup env = ext -> ext.configOverrides("foo:1") - here configuration object would be implicitly returned (because all methods return object itself for chained calls) and ignored.

      Specified by:
      setup in interface TestEnvironmentSetup
      Parameters:
      extension - test extension configuration object (support chained calls)
      Returns:
      AutoCloseable or ExtensionContext.Store.CloseableResource if something needs to be shut down after test, any other object would be ignored (including null)
    • fieldDetected

      protected abstract void fieldDetected(org.junit.jupiter.api.extension.ExtensionContext context, AnnotatedField<A,T> field)
      Validate resolved field, if required. Note that some validations are performed automatically like checking field type with provided required type or unreachable annotated fields reporting. This method should be used for validations, which are not possible to perform automatically (e.g., there is a class, declared in annotation that must comply with a field type (base class know nothing about annotation and can't check that).

      Called only for current test class own fields: in case of nested test, root test fields would already be validated. Also, if guice context started per each test method, validation would be called only for the first test method because fields would be searched just once - no need to validate each time.

      Parameters:
      context - junit context
      field - annotated fields
    • registerHooks

      protected abstract void registerHooks(TestExtension extension)
      Called to register additional guicey hooks, if required. Called only when at least one annotated field is detected.
      Parameters:
      extension - extension configuration object
    • initializeField

      protected abstract <K> void initializeField(AnnotatedField<A,T> field, T userValue)
      Configure application for a field (user value might be provided). There might be field object instance creation (e.g. mocks initialization), guice overrides registration, etc. The main initialization point.

      NOTE: If user-provided values are not allowed, throw an exception here

      Type Parameters:
      K - type for aligning a binding key with value types (cheating on guice type checks)
      Parameters:
      field - annotated field
      userValue - user-provided field value (pre-initialized)
    • beforeValueInjection

      protected abstract void beforeValueInjection(EventContext context, AnnotatedField<A,T> field)
      Called after application startup and before field value injection. Useful for additional validations, which can't be performed before, like binding correctness validation (requiring the created injector): for example, to detect instance bindings when extension relies on AOP and so would not work. Such validation is impossible to do before (in time of binding overrides).

      Called before injectFieldValue(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext, AnnotatedField). At this point, non-static fields could be resolved (test instance present).

      Important: method called for all fields, even initialized by user! Inject value method might not be called after it!

      Parameters:
      context - event context
      field - annotated field
    • injectFieldValue

      protected abstract T injectFieldValue(EventContext context, AnnotatedField<A,T> field)
      Get test field value (would be immediately injected into the test field). Called only if field was not initialized by user (not a manual value). For example, implementation might simply get bean instance from guice context (if guice was re-configured with module overrides).

      Warning: not called for manually initialized fields (because value already set)! To validate binding use beforeValueInjection(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext, AnnotatedField) method instead (which is called for all fields).

      Application already completely started and test extension initialized at this moment (beforeEach test phase).

      Parameters:
      context - event context
      field - annotated field
      Returns:
      created field value
    • report

      protected abstract void report(EventContext context, List<AnnotatedField<A,T>> fields)
      Called when debug is enabled on guicey extension to report registered fields. Note: there might be fields from multiple test classes in case of nested tests.

      Report called after application startup because at this point all fields were processed (in configure guice method) and so all required fields data collected. Called only if at least one field is detected.

      Special custom data markers used in field objects (AnnotatedField.getCustomData(String)):

      Parameters:
      context - event context, IMPORTANT - this would be setup context and not current
      fields - fields to report
    • beforeTest

      protected abstract void beforeTest(EventContext context, AnnotatedField<A,T> field, T value)
      Called before each test to pre-process field value (if required).
      Parameters:
      context - event context
      field - filed descriptor
      value - value instance
    • afterTest

      protected abstract void afterTest(EventContext context, AnnotatedField<A,T> field, T value)
      Called after each test to post-process field value (if required).
      Parameters:
      context - event context
      field - filed descriptor
      value - value instance
    • starting

      public void starting(EventContext context) throws Exception
      Description copied from interface: TestExecutionListener
      Called before dropwizard (or guicey) application starting. It could be beforeAll or beforeEach phase (if important, look ExtensionContext.getTestMethod() to make sure). Application could start/stop multiple times within one test class (if extension registered in non-static field).

      NOTE: At this stage, injections not yet performed inside test instance.

      This method could be used instead of beforeAll because normally extension is created under beforeAll, but for extensions created under beforeEach - it would be impossible to notify about beforeAll anyway.

      Specified by:
      starting in interface TestExecutionListener
      Parameters:
      context - context object providing access to all available objects (junit context, test support, etc.)
      Throws:
      Exception - on error
    • started

      public void started(EventContext context)
      Description copied from interface: TestExecutionListener
      Called when dropwizard (or guicey) application started. It could be beforeAll or beforeEach phase (if important, look ExtensionContext.getTestMethod() to make sure). Application could start/stop multiple times within one test class (if extension registered in non-static field).

      NOTE: At this stage, injections not yet performed inside test instance.

      This method could be used instead of beforeAll because normally extension is created under beforeAll, but for extensions created under beforeEach - it would be impossible to notify about beforeAll anyway.

      Specified by:
      started in interface TestExecutionListener
      Parameters:
      context - context object providing access to all required objects (junit context, injector, test support, etc.)
    • beforeAll

      public void beforeAll(EventContext context)
      Description copied from interface: TestExecutionListener
      IMPORTANT: this method MIGHT NOT BE CALLED at all in case if extension is registered under non-static field (and so application created before each method). Prefer TestExecutionListener.started(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext) instead, which is always called (but not always under beforeAll),

      Method could be useful if some action must be performed before each test (in case of nested tests or global application when "start" would not be called for each test).

      Specified by:
      beforeAll in interface TestExecutionListener
      Parameters:
      context - context object providing access to all required objects (junit context, injector, test support, etc.)
    • beforeEach

      public void beforeEach(EventContext context)
      Description copied from interface: TestExecutionListener
      Called before each test method execution. Guice injections into test instance already performed. Even if an application is created in beforeEach phase, this method would be called after application creation.
      Specified by:
      beforeEach in interface TestExecutionListener
      Parameters:
      context - context object providing access to all required objects (junit context, injector, test support, etc.)
    • afterEach

      public void afterEach(EventContext context)
      Description copied from interface: TestExecutionListener
      Called after each test method execution. Even if an application is closed on afterEach, this method would be called before it.
      Specified by:
      afterEach in interface TestExecutionListener
      Parameters:
      context - context object providing access to all required objects (junit context, injector, test support, etc.)
    • stopped

      public void stopped(EventContext context)
      Description copied from interface: TestExecutionListener
      Called when dropwizard (or guicey) application stopped. It could be afterAll or afterEach phase (if important, look ExtensionContext.getTestMethod() to make sure). Application could start/stop multiple times within one test class (if extension registered in non-static field).

      Note that in case of global application usage or for nested tests this method might not be called because application lifecycle would be managed by the top-most test.

      This method could be used instead of afterAll because normally extension is stopped under afterAll, but for extensions stopped under afterEach - it would be impossible to notify about afterAll anyway.

      Specified by:
      stopped in interface TestExecutionListener
      Parameters:
      context - context object providing access to all required objects (junit context, injector, test support, etc.)
    • lookupFields

      protected List<AnnotatedField<A,T>> lookupFields(org.junit.jupiter.api.extension.ExtensionContext context, jakarta.inject.Provider<List<AnnotatedField<A,T>>> fieldsProvider)
      Resolve test own fields or use already resolved fields set (for example, when guicey extension created for each method we can search and validate fields just once). Also adds fields from all paren contexts (for nested tests, which must see parent test fields and so we need to manage its lifecycle)
      Parameters:
      context - junit context
      fieldsProvider - fields searching logic
      Returns:
      all annotated fields
    • injectValues

      protected void injectValues(EventContext context, List<AnnotatedField<A,T>> fields, org.junit.jupiter.api.extension.TestInstances testInstances)
      Inject field values into test instance (under beforeEach). User defined values stay as is. Value is injected only once for test instance.
      Parameters:
      context - event context
      fields - annotated fields
      testInstances - tests instances (might be several for nested tests)
    • valueLifecycle

      protected void valueLifecycle(EventContext context, List<AnnotatedField<A,T>> fields, org.junit.jupiter.api.extension.TestInstances testInstances, boolean before)
      Called in beforeEach/afterEach to apply automatic lifecycle for field objects.
      Parameters:
      context - junit context
      fields - annotated fields
      testInstances - test instances (might be several for nested tests)
      before - true for beforeEach, false for afterEach
    • getDeclarationErrorPrefix

      protected String getDeclarationErrorPrefix(AnnotatedField<A,T> field)
      Parameters:
      field - field
      Returns:
      universal prefix for field declaration errors
    • failIfInstanceFieldInitialized

      protected void failIfInstanceFieldInitialized(AnnotatedField<A,T> field, org.junit.jupiter.api.extension.TestInstances testInstances)
      When guicey extension starts in beforeAll - it can't see instance fields (by default) and so can't check if use provide any value. On beforeEach we have to validate that value was not provided, because it's too late - guice context was already created.
      Parameters:
      field - field to check
      testInstances - test instances (might be several in case of nested tests)
    • validateUnreachableFieldsInNestedTest

      protected void validateUnreachableFieldsInNestedTest(Class<?> testClass)
      When guicey extension created in beforeAll, same extension would be used for nested tests, which means that, if nested test declares any annotated fields, they can't be injected into already started guice context and so usage error must be reported.
      Parameters:
      testClass - nested test class
    • getParentFields

      protected List<AnnotatedField<A,T>> getParentFields(org.junit.jupiter.api.extension.ExtensionContext context)
      This is important for the nested tests - each nested test may have its own set of fields (if guicey extension created per test method). When guicey extension is created for nested test, we still need to resolve parent test fields to correctly initialize them and apply value lifecycle (because nested test could see parent fields).
      Parameters:
      context - junit context
      Returns:
      all fields in parent contexts
    • getOwnFields

      protected List<AnnotatedField<A,T>> getOwnFields(org.junit.jupiter.api.extension.ExtensionContext context)
      Parameters:
      context - junit context
      Returns:
      fields which belong to test class (ignoring fields in parent contexts)
    • getStore

      protected org.junit.jupiter.api.extension.ExtensionContext.Store getStore(org.junit.jupiter.api.extension.ExtensionContext context)
      Gets test-specific extension storage.
      Parameters:
      context - junit context (class level!)
      Returns:
      extension storage object
    • getClassContext

      protected org.junit.jupiter.api.extension.ExtensionContext getClassContext(org.junit.jupiter.api.extension.ExtensionContext context)
      Class context is the same for all test methods, and so it is suitable for storing something that must survive between test methods.
      Parameters:
      context - junit context
      Returns:
      class junit context
    • isInstanceBinding

      protected boolean isInstanceBinding(com.google.inject.Binding<?> binding)
      Note: covers only the simplest cases (just for a basic validations).
      Parameters:
      binding - binding
      Returns:
      true if guice does not manage bean instance, false otherwise