Skip to content

Commit

Permalink
Added tests for MatsAnnoatedClass test helper
Browse files Browse the repository at this point in the history
Added example tests for both jUnit and Jupiter showing various ways that
the Extension/Rule can be used.

Also fixed so that AbstractMatsAnnotatedClass will get fields from the
encapsualting class to include in the Spring context, for nested tests
in Jupiter.
  • Loading branch information
staale authored and stolsvik committed Jan 9, 2025
1 parent da36de4 commit 010cd68
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 11 deletions.
5 changes: 5 additions & 0 deletions mats-test-junit/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ dependencies {
testImplementation project(':mats-util')
testImplementation project(':mats-test-broker')

// To test MatsAnnotatedClass
testImplementation project(':mats-spring')

// ..Removed in Java 11
testImplementation "javax.annotation:javax.annotation-api:$javaxAnnotationVersion"

Expand All @@ -41,6 +44,8 @@ dependencies {
// Single Spring test inside
testImplementation "org.springframework:spring-test:$springVersion"
testImplementation "org.springframework:spring-context:$springVersion"
testImplementation "org.springframework:spring-tx:$springVersion"

}

publishing {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package io.mats3.test.junit;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.inject.Inject;

import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;

import io.mats3.spring.Dto;
import io.mats3.spring.MatsMapping;
import io.mats3.util.MatsFuturizer.Reply;

/**
* Test of {@link Rule_MatsAnnotatedClass}
*
* @author Ståle Undheim <[email protected]> 2025-01-09
*/
public class U_RuleMatsAnnotatedClassTest {

@ClassRule
public static final Rule_Mats MATS = Rule_Mats.create();
public static final String ENDPOINT_ID = "AnnotatedEndpoint";

public static class ServiceDependency {
String formatMessage(String msg) {
return "Hello " + msg;
}
}

public static class MatsAnnotatedClass_Endpoint {

private ServiceDependency _serviceDependency;

public MatsAnnotatedClass_Endpoint() {

}

@Inject
public MatsAnnotatedClass_Endpoint(ServiceDependency serviceDependency) {
_serviceDependency = serviceDependency;
}

@MatsMapping(ENDPOINT_ID)
public String matsEndpoint(@Dto String msg) {
return _serviceDependency.formatMessage(msg);
}

}

// This dependency will be picked up by Extension_MatsAnnotatedClass, and result in injecting this
// instance into the service. This would also work if this was instead a Mockito mock.
private final ServiceDependency _serviceDependency = new ServiceDependency();

@Rule
public final Rule_MatsAnnotatedClass _matsAnnotationRule = Rule_MatsAnnotatedClass.create(MATS);

/**
* Example of adding a mats annotated class inside a test case, that will then be created and started.
*/
@Test
public void testAnnotatedMatsClass() throws ExecutionException, InterruptedException, TimeoutException {
// :: Setup
_matsAnnotationRule.withAnnotatedMatsClasses(MatsAnnotatedClass_Endpoint.class);
String expectedReturn = "Hello World!";

// :: Act
String reply = callMatsAnnotatedEndpoint("World!");

// :: Verify
Assert.assertEquals(expectedReturn, reply);
}

/**
* Example of using an already instantiated Mats annotated class inside a test method.
*/
@Test
public void testAnnotatedMatsInstance() throws ExecutionException, InterruptedException, TimeoutException {
// :: Setup
MatsAnnotatedClass_Endpoint annotatedMatsInstance = new MatsAnnotatedClass_Endpoint(_serviceDependency);
_matsAnnotationRule.withAnnotatedMatsInstances(annotatedMatsInstance);
String expectedReturn = "Hello World!";

// :: Act
String reply = callMatsAnnotatedEndpoint("World!");

// :: Verify
Assert.assertEquals(expectedReturn, reply);
}

private static String callMatsAnnotatedEndpoint(String message)
throws InterruptedException, ExecutionException, TimeoutException {
return MATS.getMatsFuturizer().futurizeNonessential(
"invokeAnnotatedEndpoint",
U_RuleMatsAnnotatedClassTest.class.getSimpleName(),
ENDPOINT_ID,
String.class,
message)
.thenApply(Reply::getReply)
.get(10, TimeUnit.SECONDS);
}

}
4 changes: 4 additions & 0 deletions mats-test-jupiter/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ dependencies {
testImplementation project(':mats-util')
testImplementation project(':mats-test-broker')

// To test MatsAnnotatedClass
testImplementation project(':mats-spring')

// ..Removed in Java 11
testImplementation "javax.annotation:javax.annotation-api:$javaxAnnotationVersion"

Expand All @@ -43,6 +46,7 @@ dependencies {
exclude group:'junit', module:'junit'
}
testImplementation "org.springframework:spring-context:$springVersion"
testImplementation "org.springframework:spring-tx:$springVersion"

// The Jupiter Runtime..?
testImplementation "org.junit.jupiter:junit-jupiter-api:$jupiterVersion"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package io.mats3.test.jupiter;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.inject.Inject;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.mats3.spring.Dto;
import io.mats3.spring.MatsMapping;
import io.mats3.util.MatsFuturizer.Reply;

/**
* Test of {@link Extension_MatsAnnotatedClass}, with example of adding annotated classes when the extension is
* created, or within each test method.
*
* @author Ståle Undheim <[email protected]> 2025-01-09
*/
class J_ExtensionMatsAnnotatedClassTest {

@RegisterExtension
private static final Extension_Mats MATS = Extension_Mats.create();
public static final String ENDPOINT_ID = "AnnotatedEndpoint";

public static class ServiceDependency {
String formatMessage(String msg) {
return "Hello " + msg;
}
}

public static class MatsAnnotatedClass_Endpoint {

private ServiceDependency _serviceDependency;

public MatsAnnotatedClass_Endpoint() {

}

@Inject
public MatsAnnotatedClass_Endpoint(ServiceDependency serviceDependency) {
_serviceDependency = serviceDependency;
}

@MatsMapping(ENDPOINT_ID)
public String matsEndpoint(@Dto String msg) {
return _serviceDependency.formatMessage(msg);
}

}

// This dependency will be picked up by Extension_MatsAnnotatedClass, and result in injecting this
// instance into the service. This would also work if this was instead a Mockito mock.
private final ServiceDependency _serviceDependency = new ServiceDependency();


/**
* Example for how to provide a class with annotated MatsEndpoints to test.
*/
@Nested
class TestAnnotatedWithProvidedClass {
@RegisterExtension
private final Extension_MatsAnnotatedClass _matsAnnotatedClassExtension = Extension_MatsAnnotatedClass
.create(MATS)
.withAnnotatedMatsClasses(MatsAnnotatedClass_Endpoint.class);

@Test
void testAnnotatedMatsClass() throws ExecutionException, InterruptedException, TimeoutException {
// :: Setup
String expectedReturn = "Hello World!";

// :: Act
String reply = callMatsAnnotatedEndpoint("World!");

// :: Verify
Assertions.assertEquals(expectedReturn, reply);
}
}

/**
* Example for how to provide an instance of an annotated class with MatsEndpoints to test.
*/
@Nested
class TestAnnotatedWithProvidedInstance {

@RegisterExtension
private final Extension_MatsAnnotatedClass _matsAnnotatedClassExtension = Extension_MatsAnnotatedClass
.create(MATS)
.withAnnotatedMatsInstances(new MatsAnnotatedClass_Endpoint(_serviceDependency));

@Test
void testAnnotatedMatsInstance() throws ExecutionException, InterruptedException, TimeoutException {
// :: Setup
String expectedReturn = "Hello World!";

// :: Act
String reply = callMatsAnnotatedEndpoint("World!");

// :: Verify
Assertions.assertEquals(expectedReturn, reply);
}
}

/**
* These tests demonstrate that we can add new annotated classes within a test.
*/
@Nested
class TestAnnotatedWithDelayedConfiguration {

@RegisterExtension
private final Extension_MatsAnnotatedClass _matsAnnotatedClassExtension
= Extension_MatsAnnotatedClass.create(MATS);

@Test
void testAnnotatedMatsClass() throws ExecutionException, InterruptedException, TimeoutException {
// :: Setup
_matsAnnotatedClassExtension.withAnnotatedMatsClasses(MatsAnnotatedClass_Endpoint.class);
String expectedReturn = "Hello World!";

// :: Act
String reply = callMatsAnnotatedEndpoint("World!");

// :: Verify
Assertions.assertEquals(expectedReturn, reply);
}


@Test
void testAnnotatedMatsInstance() throws ExecutionException, InterruptedException, TimeoutException {
// :: Setup
MatsAnnotatedClass_Endpoint annotatedClassInstance =
new MatsAnnotatedClass_Endpoint(_serviceDependency);
_matsAnnotatedClassExtension.withAnnotatedMatsInstances(annotatedClassInstance);
String expectedReturn = "Hello World!";

// :: Act
String reply = callMatsAnnotatedEndpoint("World!");

// :: Verify
Assertions.assertEquals(expectedReturn, reply);
}
}

private String callMatsAnnotatedEndpoint(String message)
throws InterruptedException, ExecutionException, TimeoutException {
return MATS.getMatsFuturizer().futurizeNonessential(
"invokeAnnotatedEndpoint",
getClass().getSimpleName(),
ENDPOINT_ID,
String.class,
message)
.thenApply(Reply::getReply)
.get(10, TimeUnit.SECONDS);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,39 @@ public void beforeEach(Object testInstance) {
// If we have a test instance, we register each non-null field as a singleton in the Spring context.
// This is so that those fields are available to inject into the Mats annotated classes.
if (testInstance != null) {
ReflectionUtils.doWithFields(testInstance.getClass(), field -> {
field.setAccessible(true);
String beanName = field.getName();
Object beanInstance = field.get(testInstance);

// ?: Is there no bean registered with this name, and we have a value for the field?
if (!_applicationContext.containsBean(beanName) && beanInstance != null) {
// Yes -> add this to the Spring context as a singleton
_applicationContext.getBeanFactory().registerSingleton(beanName, beanInstance);
}
});
addBeans(testInstance);
}
initializeBeansAndRegisterEndpoints();
}

private void addBeans(Object testInstance) {
Class<?> enclosingClass = testInstance.getClass().getEnclosingClass();

ReflectionUtils.doWithFields(testInstance.getClass(), field -> {
field.setAccessible(true);
String beanName = field.getName();
Object beanInstance = field.get(testInstance);
// Do not inject this utility into Spring
if (beanInstance == this) {
return;
}

// ?: Is this a synthetic field created by the compiler for the enclosing class?
if (field.getType().equals(enclosingClass) && field.isSynthetic()) {
// Yes, then add the fields from the enclosing class as well to the Spring context (but not
// the test itself). This is to support nested tests in Jupiter.
addBeans(beanInstance);
return;
}

// ?: Is there no bean registered with this name, and we have a value for the field?
if (!_applicationContext.containsBean(beanName) && beanInstance != null) {
// Yes -> add this to the Spring context as a singleton
_applicationContext.getBeanFactory().registerSingleton(beanName, beanInstance);
}
});
}

public void afterEach() {
for (MatsEndpoint<?, ?> endpoint : _registeredEndpoints) {
endpoint.remove(30_000);
Expand Down

0 comments on commit 010cd68

Please sign in to comment.