Skip to content

Commit

Permalink
QuarkusComponentTest: register additional annotations transformers
Browse files Browse the repository at this point in the history
- introduce JaxrsSingletonTransformer to add Singleton to a JAX-RS
component that has no scope
- resolves quarkusio#35313
  • Loading branch information
mkouba authored and holly-cummins committed Feb 8, 2024
1 parent 4d6c0cd commit ef6ce14
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 4 deletions.
5 changes: 5 additions & 0 deletions test-framework/junit5-component/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkus.test.component;

import java.util.List;

import jakarta.inject.Singleton;

import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.DotName;

import io.quarkus.arc.processor.Annotations;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.BuiltinScope;

/**
* Add {@link Singleton} to a JAX-RS component that has no scope annotation.
*/
public class JaxrsSingletonTransformer implements AnnotationsTransformer {

private final List<DotName> ANNOTATIONS = List.of(DotName.createSimple("jakarta.ws.rs.Path"),
DotName.createSimple("jakarta.ws.rs.ApplicationPath"), DotName.createSimple("jakarta.ws.rs.ext.Provider"));

@Override
public boolean appliesTo(Kind kind) {
return Kind.CLASS == kind;
}

@Override
public void transform(TransformationContext context) {
// Note that custom scopes are not supported yet
if (BuiltinScope.isIn(context.getAnnotations())) {
return;
}
if (Annotations.containsAny(context.getAnnotations(), ANNOTATIONS)) {
context.transform().add(Singleton.class).done();
}
}

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

import org.junit.jupiter.api.extension.ExtendWith;

import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.test.InjectMock;
import io.smallrye.common.annotation.Experimental;

Expand Down Expand Up @@ -57,4 +58,15 @@
* @see QuarkusComponentTestExtension#setConfigSourceOrdinal(int)
*/
int configSourceOrdinal() default QuarkusComponentTestExtension.DEFAULT_CONFIG_SOURCE_ORDINAL;

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,14 @@ public class QuarkusComponentTestExtension
private final AtomicBoolean useDefaultConfigProperties = new AtomicBoolean();
private final AtomicBoolean addNestedClassesAsComponents = new AtomicBoolean(true);
private final AtomicInteger configSourceOrdinal = new AtomicInteger(DEFAULT_CONFIG_SOURCE_ORDINAL);
private final List<AnnotationsTransformer> additionalAnnotationsTransformers;

// Used for declarative registration
public QuarkusComponentTestExtension() {
this.additionalComponentClasses = List.of();
this.configProperties = new HashMap<>();
this.mockConfigurators = new ArrayList<>();
this.additionalAnnotationsTransformers = new ArrayList<>();
}

/**
Expand All @@ -189,6 +191,7 @@ public QuarkusComponentTestExtension(Class<?>... additionalComponentClasses) {
this.additionalComponentClasses = List.of(additionalComponentClasses);
this.configProperties = new HashMap<>();
this.mockConfigurators = new ArrayList<>();
this.additionalAnnotationsTransformers = new ArrayList<>();
}

/**
Expand All @@ -210,7 +213,7 @@ public <T> MockBeanConfigurator<T> mock(Class<T> beanClass) {
*
* @param key
* @param value
* @return the extension
* @return self
*/
public QuarkusComponentTestExtension configProperty(String key, String value) {
this.configProperties.put(key, value);
Expand All @@ -222,7 +225,7 @@ public QuarkusComponentTestExtension configProperty(String key, String value) {
* <p>
* For primitives the default values as defined in the JLS are used. For any other type {@code null} is injected.
*
* @return the extension
* @return self
*/
public QuarkusComponentTestExtension useDefaultConfigProperties() {
this.useDefaultConfigProperties.set(true);
Expand All @@ -235,7 +238,7 @@ public QuarkusComponentTestExtension useDefaultConfigProperties() {
* By default, all static nested classes declared on the test class are added to the set of additional components under
* test.
*
* @return the extension
* @return self
*/
public QuarkusComponentTestExtension ignoreNestedClasses() {
this.addNestedClassesAsComponents.set(false);
Expand All @@ -247,13 +250,24 @@ public QuarkusComponentTestExtension ignoreNestedClasses() {
* {@value #DEFAULT_CONFIG_SOURCE_ORDINAL} is used.
*
* @param val
* @return the extension
* @return self
*/
public QuarkusComponentTestExtension setConfigSourceOrdinal(int val) {
this.configSourceOrdinal.set(val);
return this;
}

/**
* Add an additional {@link AnnotationsTransformer}.
*
* @param transformer
* @return self
*/
public QuarkusComponentTestExtension addAnnotationsTransformer(AnnotationsTransformer transformer) {
this.additionalAnnotationsTransformers.add(transformer);
return this;
}

@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
long start = System.nanoTime();
Expand Down Expand Up @@ -295,6 +309,16 @@ public void beforeAll(ExtensionContext context) throws Exception {
}
this.addNestedClassesAsComponents.set(testAnnotation.addNestedClassesAsComponents());
this.configSourceOrdinal.set(testAnnotation.configSourceOrdinal());
Class<? extends AnnotationsTransformer>[] transformers = testAnnotation.annotationsTransformers();
if (transformers.length > 0) {
for (Class<? extends AnnotationsTransformer> transformerClass : transformers) {
try {
this.additionalAnnotationsTransformers.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;
Expand Down Expand Up @@ -607,6 +631,11 @@ public void writeResource(Resource resource) throws IOException {
builder.addAnnotationTransformer(AnnotationsTransformer.appliedToField().whenContainsAny(qualifiers)
.whenContainsNone(DotName.createSimple(Inject.class)).thenTransform(t -> t.add(Inject.class)));

builder.addAnnotationTransformer(new JaxrsSingletonTransformer());
for (AnnotationsTransformer transformer : additionalAnnotationsTransformers) {
builder.addAnnotationTransformer(transformer);
}

// Register:
// 1) Dummy mock beans for all unsatisfied injection points
// 2) Synthetic beans for Config and @ConfigProperty injection points
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.quarkus.test.component;

import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;

import org.jboss.jandex.DotName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mockito;

import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.test.InjectMock;
import io.quarkus.test.component.beans.Charlie;

public class AnnotationsTransformerTest {

@RegisterExtension
static final QuarkusComponentTestExtension extension = new QuarkusComponentTestExtension()
.addAnnotationsTransformer(AnnotationsTransformer.appliedToClass()
.whenClass(c -> c.declaredAnnotations().isEmpty()
&& c.annotationsMap().containsKey(DotName.createSimple(Inject.class)))
.thenTransform(t -> t.add(Singleton.class)));

@Inject
NotABean bean;

@InjectMock
Charlie charlie;

@Test
public void testPing() {
Mockito.when(charlie.ping()).thenReturn("foo");
assertEquals("foo", bean.ping());
}

// @Singleton should be added automatically
public static class NotABean {

@Inject
Charlie charlie;

public String ping() {
return charlie.ping();
}

}

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

import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;

import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.test.InjectMock;
import io.quarkus.test.component.QuarkusComponentTest;
import io.quarkus.test.component.beans.Charlie;
import io.quarkus.test.component.declarative.AnnotationsTransformerTest.MyAnnotationsTransformer;

@QuarkusComponentTest(annotationsTransformers = MyAnnotationsTransformer.class)
public class AnnotationsTransformerTest {

@Inject
NotABean bean;

@InjectMock
Charlie charlie;

@Test
public void testPing() {
Mockito.when(charlie.ping()).thenReturn("foo");
assertEquals("foo", bean.ping());
}

// @Singleton should be added automatically
public static class NotABean {

@Inject
Charlie charlie;

public String ping() {
return charlie.ping();
}

}

public static class MyAnnotationsTransformer implements AnnotationsTransformer {

@Override
public boolean appliesTo(Kind kind) {
return Kind.CLASS == kind;
}

@Override
public void transform(TransformationContext context) {
ClassInfo c = context.getTarget().asClass();
if (c.declaredAnnotations().isEmpty()
&& c.annotationsMap().containsKey(DotName.createSimple(Inject.class))) {
context.transform().add(Singleton.class).done();
}
}

}

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

import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import io.quarkus.test.InjectMock;
import io.quarkus.test.component.QuarkusComponentTest;
import io.quarkus.test.component.beans.Charlie;

@QuarkusComponentTest
public class JaxrsSingletonTransformerTest {

@Inject
MyResource resource;

@InjectMock
Charlie charlie;

@Test
public void testPing() {
Mockito.when(charlie.ping()).thenReturn("foo");
assertEquals("foo", resource.ping());
}

// @Singleton should be added automatically
@Path("my")
public static class MyResource {

@Inject
Charlie charlie;

@GET
public String ping() {
return charlie.ping();
}

}

}

0 comments on commit ef6ce14

Please sign in to comment.