Class AnnotatedTestFieldSetup<A extends java.lang.annotation.Annotation,T>
- java.lang.Object
-
- ru.vyarus.dropwizard.guice.test.jupiter.env.field.AnnotatedTestFieldSetup<A,T>
-
- Type Parameters:
A- annotation typeT- 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
TestEnvironmentSetupobject (hook and listener would be registered automatically).- Since:
- 10.02.2025
-
-
Field Summary
Fields Modifier and Type Field Description protected booleanappPerClassTrue if guicey extension start application per test class (for all methods).protected static java.lang.StringFIELD_INJECTEDIndicates injected field value (or pre-initialized value presence).protected static java.lang.StringFIELD_MANUALIndicates user-provided value (field was pre-initialized by user).protected java.util.List<AnnotatedField<A,T>>fieldsResolved annotated fields.protected java.lang.Class<?>regTestClassTest class.protected org.junit.jupiter.api.extension.ExtensionContextsetupContextJunit context, used for fields search.protected java.lang.StringsetupContextNameHuman-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 voidafterEach(EventContext context)Called after each test method execution.protected abstract voidafterTest(EventContext context, AnnotatedField<A,T> field, T value)Called after each test to post-process field value (if required).voidbeforeAll(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).voidbeforeEach(EventContext context)Called before each test method execution.protected abstract voidbeforeTest(EventContext context, AnnotatedField<A,T> field, T value)Called before each test to pre-process field value (if required).protected abstract voidbeforeValueInjection(EventContext context, AnnotatedField<A,T> field)Called after application startup and before field value injection.protected voidfailIfInstanceFieldInitialized(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 voidfieldDetected(org.junit.jupiter.api.extension.ExtensionContext context, AnnotatedField<A,T> field)Validate resolved field, if required.protected org.junit.jupiter.api.extension.ExtensionContextgetClassContext(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.StringgetDeclarationErrorPrefix(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.StoregetStore(org.junit.jupiter.api.extension.ExtensionContext context)Gets test-specific extension storage.protected abstract <K> voidinitializeField(AnnotatedField<A,T> field, T userValue)Configure application for a field (user value might be provided).protected abstract TinjectFieldValue(EventContext context, AnnotatedField<A,T> field)Get test field value (would be immediately injected into the test field).protected voidinjectValues(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 booleanisInstanceBinding(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 voidregisterHooks(TestExtension extension)Called to register additional guicey hooks, if required.protected abstract voidreport(EventContext context, java.util.List<AnnotatedField<A,T>> fields)Called when debug is enabled on guicey extension to report registered fields.java.lang.Objectsetup(TestExtension extension)Called before test application startup under junit "before all" phase or "before each" (depends on extension registration).voidstarted(EventContext context)Called when dropwizard (or guicey) application started.voidstarting(EventContext context)Called before dropwizard (or guicey) application starting.voidstopped(EventContext context)Called when dropwizard (or guicey) application stopped.protected voidvalidateUnreachableFieldsInNestedTest(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 voidvalueLifecycle(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
-
Methods inherited from interface ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener
afterAll, stopping
-
-
-
-
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 classfieldType- 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:TestEnvironmentSetupCalled 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:
setupin interfaceTestEnvironmentSetup- Parameters:
extension- test extension configuration object (support chained calls)- Returns:
AutoCloseableorExtensionContext.Store.CloseableResourceif 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 contextfield- 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 fielduserValue- 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 contextfield- 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 contextfield- 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)):FIELD_MANUALfield value was initialized by user, otherwise automaticFIELD_INJECTEDfield injection instance (test instance)
- Parameters:
context- event context, IMPORTANT - this would be setup context and not currentfields- 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 contextfield- filed descriptorvalue- 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 contextfield- filed descriptorvalue- value instance
-
starting
public void starting(EventContext context) throws java.lang.Exception
Description copied from interface:TestExecutionListenerCalled before dropwizard (or guicey) application starting. It could be beforeAll or beforeEach phase (if important, lookExtensionContext.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:
startingin interfaceTestExecutionListener- 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:TestExecutionListenerCalled when dropwizard (or guicey) application started. It could be beforeAll or beforeEach phase (if important, lookExtensionContext.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:
startedin interfaceTestExecutionListener- 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:TestExecutionListenerIMPORTANT: 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). PreferTestExecutionListener.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:
beforeAllin interfaceTestExecutionListener- 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:TestExecutionListenerCalled 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:
beforeEachin interfaceTestExecutionListener- 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:TestExecutionListenerCalled after each test method execution. Even if an application is closed on afterEach, this method would be called before it.- Specified by:
afterEachin interfaceTestExecutionListener- 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:TestExecutionListenerCalled when dropwizard (or guicey) application stopped. It could be afterAll or afterEach phase (if important, lookExtensionContext.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:
stoppedin interfaceTestExecutionListener- 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 contextfieldsProvider- 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 contextfields- annotated fieldstestInstances- 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 contextfields- annotated fieldstestInstances- 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 checktestInstances- 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
-
-