Class AnnotatedTestFieldSetup<A extends java.lang.annotation.Annotation,​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

    public abstract class AnnotatedTestFieldSetup<A extends java.lang.annotation.Annotation,​T>
    extends java.lang.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 appPerClass
      True if guicey extension start application per test class (for all methods).
      protected static java.lang.String FIELD_INJECTED
      Indicates injected field value (or pre-initialized value presence).
      protected static java.lang.String FIELD_MANUAL
      Indicates user-provided value (field was pre-initialized by user).
      protected java.util.List<AnnotatedField<A,​T>> fields
      Resolved annotated fields.
      protected java.lang.Class<?> regTestClass
      Test class.
      protected org.junit.jupiter.api.extension.ExtensionContext setupContext
      Junit context, used for fields search.
      protected java.lang.String setupContextName
      Human-readable (if @DisplayName or spock used) class or method name.
    • Constructor Summary

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

      All Methods Instance Methods Abstract Methods Concrete Methods 
      Modifier and Type Method Description
      void afterEach​(EventContext context)
      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 beforeAll​(EventContext context)
      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 beforeEach​(EventContext context)
      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 beforeValueInjection​(EventContext context, AnnotatedField<A,​T> field)
      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 java.lang.String getDeclarationErrorPrefix​(AnnotatedField<A,​T> field)  
      protected java.util.List<AnnotatedField<A,​T>> getOwnFields​(org.junit.jupiter.api.extension.ExtensionContext context)  
      protected java.util.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 injectFieldValue​(EventContext context, AnnotatedField<A,​T> field)
      Get test field value (would be immediately injected into the test field).
      protected void injectValues​(EventContext context, java.util.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 java.util.List<AnnotatedField<A,​T>> lookupFields​(org.junit.jupiter.api.extension.ExtensionContext context, javax.inject.Provider<java.util.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 registerHooks​(TestExtension extension)
      Called to register additional guicey hooks, if required.
      protected abstract void report​(EventContext context, java.util.List<AnnotatedField<A,​T>> fields)
      Called when debug is enabled on guicey extension to report registered fields.
      java.lang.Object setup​(TestExtension extension)
      Called before test application startup under junit "before all" phase or "before each" (depends on extension registration).
      void started​(EventContext context)
      Called when dropwizard (or guicey) application started.
      void starting​(EventContext context)
      Called before dropwizard (or guicey) application starting.
      void stopped​(EventContext context)
      Called when dropwizard (or guicey) application stopped.
      protected void validateUnreachableFieldsInNestedTest​(java.lang.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.
      protected void valueLifecycle​(EventContext context, java.util.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
    • Field Detail

      • FIELD_MANUAL

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

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

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

        protected java.lang.Class<?> regTestClass
        Test class.
      • setupContextName

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

        protected java.util.List<AnnotatedField<A extends java.lang.annotation.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 Detail

      • AnnotatedTestFieldSetup

        public AnnotatedTestFieldSetup​(java.lang.Class<A> fieldAnnotation,
                                       java.lang.Class<T> fieldType,
                                       java.lang.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 Detail

      • setup

        public java.lang.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,
                                       java.util.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 java.lang.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:
        java.lang.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 java.util.List<AnnotatedField<A,​T>> lookupFields​(org.junit.jupiter.api.extension.ExtensionContext context,
                                                                         javax.inject.Provider<java.util.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,
                                    java.util.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,
                                      java.util.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 java.lang.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​(java.lang.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 java.util.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 java.util.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