Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QuarkusComponentTest: refactorings and API changes #35768

Merged
merged 1 commit into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions docs/src/main/asciidoc/getting-started-testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,7 @@
public class FooTest {

@RegisterExtension <1>
static final QuarkusComponentTestExtension extension = new QuarkusComponentTestExtension().configProperty("bar","true");
static final QuarkusComponentTestExtension extension = QuarkusComponentTestExtension.builder().configProperty("bar","true").build();

@Inject
Foo foo;
Expand All @@ -1621,10 +1621,11 @@
=== Lifecycle

So what exactly does the `QuarkusComponentTest` do?
It starts the CDI container and registers a dedicated xref:config-reference.adoc[configuration object] during the `before all` test phase.
The container is stopped and the config is released during the `after all` test phase.
The fields annotated with `@Inject` and `@InjectMock` are injected after a test instance is created and unset before a test instance is destroyed.
It starts the CDI container and registers a dedicated xref:config-reference.adoc[configuration object].
If the test instance lifecycle is `Lifecycle#PER_METHOD` (default) then the container is started during the `before each` test phase and stopped during the `after each` test phase.
However, if the test instance lifecycle is `Lifecycle#PER_CLASS` then the container is started during the `before all` test phase and stopped during the `after all` test phase.
The fields annotated with `@Inject` and `@InjectMock` are injected after a test instance is created.
Finally, the CDI request context is activated and terminated per each test method.

Check warning on line 1628 in docs/src/main/asciidoc/getting-started-testing.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.HeadingPunctuation] Do not use end punctuation in headings. Raw Output: {"message": "[Quarkus.HeadingPunctuation] Do not use end punctuation in headings.", "location": {"path": "docs/src/main/asciidoc/getting-started-testing.adoc", "range": {"start": {"line": 1628, "column": 70}}}, "severity": "INFO"}

Check warning on line 1628 in docs/src/main/asciidoc/getting-started-testing.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in '20.2. Auto Mocking Unsatisfied Dependencies'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in '20.2. Auto Mocking Unsatisfied Dependencies'.", "location": {"path": "docs/src/main/asciidoc/getting-started-testing.adoc", "range": {"start": {"line": 1628, "column": 70}}}, "severity": "INFO"}

=== Auto Mocking Unsatisfied Dependencies

Expand All @@ -1637,13 +1638,15 @@
=== Custom Mocks For Unsatisfied Dependencies

Sometimes you need the full control over the bean attributes and maybe even configure the default mock behavior.
You can use the mock configurator API via the `QuarkusComponentTestExtension#mock()` method.
You can use the mock configurator API via the `QuarkusComponentTestExtensionBuilder#mock()` method.

Check warning on line 1641 in docs/src/main/asciidoc/getting-started-testing.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'through', 'by', 'from', 'on', or 'by using' rather than 'via' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'through', 'by', 'from', 'on', or 'by using' rather than 'via' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/getting-started-testing.adoc", "range": {"start": {"line": 1641, "column": 24}}}, "severity": "WARNING"}

=== Configuration

Check warning on line 1643 in docs/src/main/asciidoc/getting-started-testing.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.HeadingPunctuation] Do not use end punctuation in headings. Raw Output: {"message": "[Quarkus.HeadingPunctuation] Do not use end punctuation in headings.", "location": {"path": "docs/src/main/asciidoc/getting-started-testing.adoc", "range": {"start": {"line": 1643, "column": 1}}}, "severity": "INFO"}

Check warning on line 1643 in docs/src/main/asciidoc/getting-started-testing.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in '20.4. Configuration'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in '20.4. Configuration'.", "location": {"path": "docs/src/main/asciidoc/getting-started-testing.adoc", "range": {"start": {"line": 1643, "column": 1}}}, "severity": "INFO"}

A dedicated `SmallRyeConfig` is registered during the `before all` test phase.
Moreover, it's possible to set the configuration properties via the `QuarkusComponentTestExtension#configProperty(String, String)` method or the `@TestConfigProperty` annotation.
If you only need to use the default values for missing config properties, then the `QuarkusComponentTestExtension#useDefaultConfigProperties()` or `@QuarkusComponentTest#useDefaultConfigProperties()` might come in useful.
You can set the configuration properties for a test with the `@io.quarkus.test.component.TestConfigProperty` annotation or with the `QuarkusComponentTestExtensionBuilder#configProperty(String, String)` method.

Check warning on line 1645 in docs/src/main/asciidoc/getting-started-testing.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/getting-started-testing.adoc", "range": {"start": {"line": 1645, "column": 208}}}, "severity": "INFO"}
If you only need to use the default values for missing config properties, then the `@QuarkusComponentTest#useDefaultConfigProperties()` or `QuarkusComponentTestExtensionBuilder#useDefaultConfigProperties()` might come in useful.

It is also possible to set configuration properties for a test method with the `@io.quarkus.test.component.TestConfigProperty` annotation.
However, if the test instance lifecycle is `Lifecycle#_PER_CLASS` this annotation can only be used on the test class and is ignored on test methods.

Check warning on line 1649 in docs/src/main/asciidoc/getting-started-testing.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in '20.5. Mocking CDI Interceptors'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in '20.5. Mocking CDI Interceptors'.", "location": {"path": "docs/src/main/asciidoc/getting-started-testing.adoc", "range": {"start": {"line": 1649, "column": 136}}}, "severity": "INFO"}

Check warning on line 1649 in docs/src/main/asciidoc/getting-started-testing.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.HeadingPunctuation] Do not use end punctuation in headings. Raw Output: {"message": "[Quarkus.HeadingPunctuation] Do not use end punctuation in headings.", "location": {"path": "docs/src/main/asciidoc/getting-started-testing.adoc", "range": {"start": {"line": 1649, "column": 136}}}, "severity": "INFO"}

=== Mocking CDI Interceptors

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,20 @@ public interface MockBeanConfigurator<T> {
* @param create
* @return the test extension
*/
QuarkusComponentTestExtension create(Function<SyntheticCreationalContext<T>, T> create);
QuarkusComponentTestExtensionBuilder create(Function<SyntheticCreationalContext<T>, T> create);

/**
* A Mockito mock object created from the bean class is used as a bean instance.
*
* @return the test extension
*/
QuarkusComponentTestExtension createMockitoMock();
QuarkusComponentTestExtensionBuilder createMockitoMock();

/**
* A Mockito mock object created from the bean class is used as a bean instance.
*
* @return the test extension
*/
QuarkusComponentTestExtension createMockitoMock(Consumer<T> mockInitializer);
QuarkusComponentTestExtensionBuilder createMockitoMock(Consumer<T> mockInitializer);

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

class MockBeanConfiguratorImpl<T> implements MockBeanConfigurator<T> {

final QuarkusComponentTestExtension test;
final QuarkusComponentTestExtensionBuilder builder;
final Class<?> beanClass;
Set<Type> types;
Set<Annotation> qualifiers;
Expand All @@ -44,8 +44,8 @@ class MockBeanConfiguratorImpl<T> implements MockBeanConfigurator<T> {
Set<org.jboss.jandex.Type> jandexTypes;
Set<AnnotationInstance> jandexQualifiers;

public MockBeanConfiguratorImpl(QuarkusComponentTestExtension test, Class<?> beanClass) {
this.test = test;
public MockBeanConfiguratorImpl(QuarkusComponentTestExtensionBuilder builder, Class<?> beanClass) {
this.builder = builder;
this.beanClass = beanClass;
this.types = new HierarchyDiscovery(beanClass).getTypeClosure();

Expand Down Expand Up @@ -142,19 +142,19 @@ public MockBeanConfigurator<T> defaultBean(boolean defaultBean) {
}

@Override
public QuarkusComponentTestExtension create(Function<SyntheticCreationalContext<T>, T> create) {
public QuarkusComponentTestExtensionBuilder create(Function<SyntheticCreationalContext<T>, T> create) {
this.create = create;
return register();
}

@Override
public QuarkusComponentTestExtension createMockitoMock() {
public QuarkusComponentTestExtensionBuilder createMockitoMock() {
this.create = c -> QuarkusComponentTestExtension.cast(Mockito.mock(beanClass));
return register();
}

@Override
public QuarkusComponentTestExtension createMockitoMock(Consumer<T> mockInitializer) {
public QuarkusComponentTestExtensionBuilder createMockitoMock(Consumer<T> mockInitializer) {
this.create = c -> {
T mock = QuarkusComponentTestExtension.cast(Mockito.mock(beanClass));
mockInitializer.accept(mock);
Expand All @@ -163,9 +163,9 @@ public QuarkusComponentTestExtension createMockitoMock(Consumer<T> mockInitializ
return register();
}

public QuarkusComponentTestExtension register() {
test.registerMockBean(this);
return test;
public QuarkusComponentTestExtensionBuilder register() {
builder.registerMockBean(this);
return builder;
}

boolean matches(BeanResolver beanResolver, org.jboss.jandex.Type requiredType, Set<AnnotationInstance> qualifiers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
* <p>
* For primitives the default values as defined in the JLS are used. For any other type {@code null} is injected.
*
* @see QuarkusComponentTestExtension#useDefaultConfigProperties()
* @see QuarkusComponentTestExtensionBuilder#useDefaultConfigProperties()
*/
boolean useDefaultConfigProperties() default false;

Expand All @@ -55,17 +55,17 @@
/**
* The ordinal of the config source used for all test config properties.
*
* @see QuarkusComponentTestExtension#setConfigSourceOrdinal(int)
* @see QuarkusComponentTestExtensionBuilder#setConfigSourceOrdinal(int)
*/
int configSourceOrdinal() default QuarkusComponentTestExtension.DEFAULT_CONFIG_SOURCE_ORDINAL;
int configSourceOrdinal() default QuarkusComponentTestExtensionBuilder.DEFAULT_CONFIG_SOURCE_ORDINAL;

/**
* The additional annotation transformers.
* <p>
* The initial set includes the {@link JaxrsSingletonTransformer}.
*
* @see AnnotationsTransformer
* @see QuarkusComponentTestExtension#addAnnotationsTransformer(AnnotationsTransformer)
* @see QuarkusComponentTestExtensionBuilder#addAnnotationsTransformer(AnnotationsTransformer)
*/
Class<? extends AnnotationsTransformer>[] annotationsTransformers() default {};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkus.test.component;

import java.util.Map;
import java.util.Set;

import org.eclipse.microprofile.config.spi.ConfigSource;

class QuarkusComponentTestConfigSource implements ConfigSource {

private final Map<String, String> configProperties;
private final int ordinal;

QuarkusComponentTestConfigSource(Map<String, String> configProperties, int ordinal) {
this.configProperties = configProperties;
this.ordinal = ordinal;
}

@Override
public Set<String> getPropertyNames() {
return configProperties.keySet();
}

@Override
public String getValue(String propertyName) {
return configProperties.get(propertyName);
}

@Override
public String getName() {
return QuarkusComponentTestExtension.class.getName();
}

@Override
public int getOrdinal() {
return ordinal;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package io.quarkus.test.component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.enterprise.event.Event;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.spi.BeanContainer;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.inject.Inject;
import jakarta.inject.Provider;

import org.jboss.logging.Logger;

import io.quarkus.arc.InjectableInstance;
import io.quarkus.arc.processor.AnnotationsTransformer;

class QuarkusComponentTestConfiguration {

static final QuarkusComponentTestConfiguration DEFAULT = new QuarkusComponentTestConfiguration(Map.of(), List.of(),
List.of(), false, true, QuarkusComponentTestExtensionBuilder.DEFAULT_CONFIG_SOURCE_ORDINAL, List.of());

private static final Logger LOG = Logger.getLogger(QuarkusComponentTestConfiguration.class);

final Map<String, String> configProperties;
final List<Class<?>> componentClasses;
final List<MockBeanConfiguratorImpl<?>> mockConfigurators;
final boolean useDefaultConfigProperties;
final boolean addNestedClassesAsComponents;
final int configSourceOrdinal;
final List<AnnotationsTransformer> annotationsTransformers;

QuarkusComponentTestConfiguration(Map<String, String> configProperties, List<Class<?>> componentClasses,
List<MockBeanConfiguratorImpl<?>> mockConfigurators, boolean useDefaultConfigProperties,
boolean addNestedClassesAsComponents, int configSourceOrdinal,
List<AnnotationsTransformer> annotationsTransformers) {
this.configProperties = configProperties;
this.componentClasses = componentClasses;
this.mockConfigurators = mockConfigurators;
this.useDefaultConfigProperties = useDefaultConfigProperties;
this.addNestedClassesAsComponents = addNestedClassesAsComponents;
this.configSourceOrdinal = configSourceOrdinal;
this.annotationsTransformers = annotationsTransformers;
}

QuarkusComponentTestConfiguration update(Class<?> testClass) {
Map<String, String> configProperties = new HashMap<>(this.configProperties);
List<Class<?>> componentClasses = new ArrayList<>(this.componentClasses);
boolean useDefaultConfigProperties = this.useDefaultConfigProperties;
boolean addNestedClassesAsComponents = this.addNestedClassesAsComponents;
int configSourceOrdinal = this.configSourceOrdinal;
List<AnnotationsTransformer> annotationsTransformers = new ArrayList<>(this.annotationsTransformers);

QuarkusComponentTest testAnnotation = testClass.getAnnotation(QuarkusComponentTest.class);
if (testAnnotation != null) {
Collections.addAll(componentClasses, testAnnotation.value());
useDefaultConfigProperties = testAnnotation.useDefaultConfigProperties();
addNestedClassesAsComponents = testAnnotation.addNestedClassesAsComponents();
configSourceOrdinal = testAnnotation.configSourceOrdinal();
Class<? extends AnnotationsTransformer>[] transformers = testAnnotation.annotationsTransformers();
if (transformers.length > 0) {
for (Class<? extends AnnotationsTransformer> transformerClass : transformers) {
try {
annotationsTransformers.add(transformerClass.getDeclaredConstructor().newInstance());
} catch (Exception e) {
LOG.errorf("Unable to instantiate %s", transformerClass);
}
}
}
}
// All fields annotated with @Inject represent component classes
Class<?> current = testClass;
while (current != null) {
for (Field field : current.getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class) && !resolvesToBuiltinBean(field.getType())) {
componentClasses.add(field.getType());
}
}
current = current.getSuperclass();
}
// All static nested classes declared on the test class are components
if (addNestedClassesAsComponents) {
for (Class<?> declaredClass : testClass.getDeclaredClasses()) {
if (Modifier.isStatic(declaredClass.getModifiers())) {
componentClasses.add(declaredClass);
}
}
}

List<TestConfigProperty> testConfigProperties = new ArrayList<>();
Collections.addAll(testConfigProperties, testClass.getAnnotationsByType(TestConfigProperty.class));
for (TestConfigProperty testConfigProperty : testConfigProperties) {
configProperties.put(testConfigProperty.key(), testConfigProperty.value());
}

return new QuarkusComponentTestConfiguration(Map.copyOf(configProperties), List.copyOf(componentClasses),
this.mockConfigurators,
useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal,
List.copyOf(annotationsTransformers));
}

QuarkusComponentTestConfiguration update(Method testMethod) {
Map<String, String> configProperties = new HashMap<>(this.configProperties);
List<TestConfigProperty> testConfigProperties = new ArrayList<>();
Collections.addAll(testConfigProperties, testMethod.getAnnotationsByType(TestConfigProperty.class));
for (TestConfigProperty testConfigProperty : testConfigProperties) {
configProperties.put(testConfigProperty.key(), testConfigProperty.value());
}
return new QuarkusComponentTestConfiguration(configProperties, componentClasses,
mockConfigurators, useDefaultConfigProperties, addNestedClassesAsComponents, configSourceOrdinal,
annotationsTransformers);
}

private static boolean resolvesToBuiltinBean(Class<?> rawType) {
return Provider.class.equals(rawType)
|| Instance.class.equals(rawType)
|| InjectableInstance.class.equals(rawType)
|| Event.class.equals(rawType)
|| BeanContainer.class.equals(rawType)
|| BeanManager.class.equals(rawType);
}

}
Loading
Loading