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

Add initial support for jpos injections in tests. #243

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
10 changes: 8 additions & 2 deletions libraries.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,17 @@ ext {
orgjsonVersion = '20200518'
postgresqlEmbeddedVersion = '1.2.8'
postgresqlBinVersion = '12.3.0'
//testing
jupiterVersion = '5.5.2'
mockitoVersion = '3.12.4'

libraries = [
//jUnit (Tests)
junit: 'org.junit.jupiter:junit-jupiter:5.5.2',
junit: "org.junit.jupiter:junit-jupiter:${jupiterVersion}",
jupiter_api: "org.junit.jupiter:junit-jupiter-api:${jupiterVersion}",

//mockito (Tests)
mockito: "org.mockito:mockito-core:${mockitoVersion}",

//jPOS
jpos: "org.jpos:jpos:${jposVersion}",
Expand Down Expand Up @@ -99,7 +106,6 @@ ext {
jetty_servlets: "org.eclipse.jetty:jetty-servlets:${jettyVersion}",
jetty_ajp: "org.eclipse.jetty:jetty-ajp:${jettyVersion}",
jetty_continuation: "org.eclipse.jetty:jetty-continuation:${jettyVersion}",
jetty_rewrite: "org.eclipse.jetty:jetty-rewrite:${jettyVersion}",

// Quartz Scheduler
quartz: 'org.quartz-scheduler:quartz:2.3.2',
Expand Down
75 changes: 75 additions & 0 deletions modules/testcore/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Module for aiding in unit tests

This module provides annotations to inject some mock or frequently needed objects during testing.

For example, it provides an injection for a `Log` object, and a `MUX` mock.

## Log injection example
In the following example, if the logger does not already exist, a default one, that logs to standard output is created with the given `logger` name, and assinged to the `Log` instance.
```java
@ExtendWith(LogSupplierExtension.class)
class LogTest {
@LogSource(logger = "Q2", realm = "log-test")
Log log;

@Test
public void testDebug() {
log.debug("debug called");
}
}
```


## Mux mocking injection example

This test class is actually executed in this module's test.

```java
package org.jpos.ee.test;

import org.jpos.iso.ISOException;
import org.jpos.iso.ISOMsg;
import org.jpos.iso.MUX;
import org.jpos.q2.iso.QMUX;
import org.jpos.util.NameRegistrar;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.when;

@ExtendWith(MUXSupplierExtension.class)
class MUXSupplierExtensionTest {

private static final String MUX_NAME = "connected-mux";
@MUXMock(connected = true, name = MUX_NAME)
MUX connectedMux;

@MUXMock(connected = false)
MUX disconnectedMux;

@Test
void testConnectedMux() throws NameRegistrar.NotFoundException {
assertSame(connectedMux, QMUX.getMUX(MUX_NAME));
assertTrue(connectedMux.isConnected());
}

@Test
void testDisconnectedMux() {
assertFalse(disconnectedMux.isConnected());
}

@Test
void testMockRequest() throws NameRegistrar.NotFoundException, ISOException {
ISOMsg request = new ISOMsg("2100");
ISOMsg response = new ISOMsg("2110");
when(connectedMux.request(same(request), anyLong())).thenReturn(response);
MUX mux = QMUX.getMUX(MUX_NAME);
assertSame(connectedMux, mux);
assertTrue(mux.isConnected());
assertSame(response, mux.request(request, 1000L));
}
}
```
7 changes: 7 additions & 0 deletions modules/testcore/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
description = 'jPOS-EE :: Testing Module'

dependencies {
implementation libraries.jupiter_api
implementation libraries.mockito
implementation libraries.jpos
}
37 changes: 37 additions & 0 deletions modules/testcore/src/main/java/org/jpos/ee/test/LogSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.jpos.ee.test;

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Injects a {@link org.jpos.util.Log} object in the declared member or parameter. It needs to be used alongside. <br>
* {@code @ExtendWith(LogSupplierExtension.class)}
* <p>
* Usage example:
* <pre> {@code
* @ExtendWith(LogSupplierExtension.class)
* class XxxxTest ...{
* ...
* @LogSource
* Log log;
* ....
*
* @AfterEach
* tearDown() {
* log.debug(...);
* }
* }
* }</pre>
* </p>
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(LogSupplierExtension.class)
public @interface LogSource {
String realm() default "";
String logger() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.jpos.ee.test;

import org.jpos.util.Logger;
import org.junit.jupiter.api.extension.*;

import java.lang.reflect.*;
import java.util.Arrays;
import java.util.function.BiConsumer;


public class LogSupplierExtension implements BeforeEachCallback, ParameterResolver, BeforeAllCallback {

protected static void runOnFields(ExtensionContext context, boolean staticFields, BiConsumer<Field, LogSource> action) {
Arrays.stream(context.getRequiredTestClass().getDeclaredFields())
.filter(f -> f.isAnnotationPresent(LogSource.class) && Modifier.isStatic(f.getModifiers()) == staticFields)
.forEach(f -> action.accept(f, f.getAnnotation(LogSource.class)));
}


protected org.jpos.util.Log getLog(LogSource annotation, Class<?> c) {
Logger logger = annotation.logger().isEmpty() ? TestUtil.getLogger() : Logger.getLogger(annotation.logger());
String realm = annotation.realm().isEmpty() ? c.getSimpleName() : annotation.realm();
return new org.jpos.util.Log(logger, realm);
}

/**
* Called to set up all MUX fields
* @param context The extension context
* @param beforeAll if this is for a beforeAll method set static fields otherwise set instance ones
*/
protected void setUp(ExtensionContext context, boolean beforeAll) {
runOnFields(context, beforeAll, (field, annotation) -> {
try {
if (!field.isAccessible()) field.setAccessible(true);
field.set( context.getTestInstance().orElse(null), getLog(annotation, context.getRequiredTestClass()));
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
});

}


/**
* Callback that is invoked <em>before</em> an individual test and any
* user-defined setup methods for that test have been executed.
*
* @param context the current extension context; never {@code null}
*/
@Override
public void beforeEach(ExtensionContext context) throws IllegalArgumentException{
setUp(context, false);
}

/**
* Determine if this resolver supports resolution of an argument for the
* {@link Parameter} in the supplied {@link ParameterContext} for the supplied
* {@link ExtensionContext}.
*
* <p>The {@link Method} or {@link Constructor}
* in which the parameter is declared can be retrieved via
* {@link ParameterContext#getDeclaringExecutable()}.
*
* @param parameterContext the context for the parameter for which an argument should
* be resolved; never {@code null}
* @param ignored unused
* @return {@code true} if this resolver can resolve an argument for the parameter
* @see #resolveParameter
* @see ParameterContext
*/
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext ignored) throws ParameterResolutionException {
return parameterContext.isAnnotated(LogSource.class) && parameterContext.getTarget().map(Object::getClass).filter(c -> c.isAssignableFrom(LogSource.class)).isPresent();
}

/**
* Resolve an argument for the parameter in the supplied {@link ParameterContext}
* for the supplied {@link ExtensionContext}.
*
* <p>This method is only called by the framework if {@link #supportsParameter}
* previously returned {@code true} for the same {@link ParameterContext}
* and {@link ExtensionContext}.
*
* <p>The {@link Method} or {@link Constructor}
* in which the parameter is declared can be retrieved via
* {@link ParameterContext#getDeclaringExecutable()}.
*
* @param parameterContext the context for the parameter for which an argument should
* be resolved; never {@code null}
* @param extensionContext the extension context for the {@code Executable}
* about to be invoked; never {@code null}
* @return the resolved argument for the parameter; may only be {@code null} if the
* parameter type is not a primitive
* @see #supportsParameter
* @see ParameterContext
*/
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return getLog(parameterContext.getParameter().getAnnotation(LogSource.class), extensionContext.getRequiredTestClass());
}


/**
* Callback that is invoked once <em>before</em> all tests in the current
* container.
*
* @param context the current extension context; never {@code null}
*/
@Override
public void beforeAll(ExtensionContext context) throws Exception {
setUp(context, true);
}
}
54 changes: 54 additions & 0 deletions modules/testcore/src/main/java/org/jpos/ee/test/MUXMock.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.jpos.ee.test;

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* <p>Injects a muck mux in the declared member or parameter. It needs to be used alongside <br>
* {@code @ExtendWith(MUXSupplierExtension.class)}. </p>
* <p>
* Usage example:
* </p>
* <pre>{@code
* @ExtendWith(MUXSupplierExtension.class)
* class XxxxTest ...{
* ...
* @MUXMock //register the mux mock in name registrar, if not already registered, and injects it
* MUX mux
* ....
*
* testXxx() {
* //define return for some condition, this example uses mockito
* when(mux.request(same(request), anyLong())).thenReturn(response);
* }
* }
* }</pre>
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(MUXSupplierExtension.class)
public @interface MUXMock {
String MUX_NAME = "";

/**
* <p>Name by which the mux is going tobe registered in Name Registrar</p>
*
* <p>Don't use the same name for instance and static, unless you don't care the static is unregistered from
* {@link org.jpos.util.NameRegistrar}</p>
*
* Defaults to {@code ""}, in which case one with a random name will be generated
*
* @return the name under which the mux mock will be registered.
*/
String name() default MUX_NAME;

/**
* Tells if the mocked mux should return true when its {@code isConnected()} method is called.
* @return the value the mocked mux {@code isConnected()} method should return.
*/
boolean connected() default true;
}
Loading
Loading