I am a Micronaut/Java beginner and I am trying to design some tests for my controllers. I could not find many examples online so here is my question.
Below is the controller with 2 #GET requests:
#Controller("/api/v1")
public class MyController {
private final ClientNetworkList clientNetworkList;
private final ClientStatus clientStatus;
public MyController(
ClientNetworkList clientNetworkList,
ClientStatus clientStatus
){
this.ClientNetworkList = clientNetworkList;
this.ClientStatus = clientStatus;
}
#Get(uri = "/networkList", produces = MediaType.APPLICATION_JSON_STREAM)
Flowable<NetworkListPackage> packagesNetworkList() {
return ClientNetworkList.fetchPackages();
}
#Get(uri = "/channels/{stringParm}/status/", produces = MediaType.APPLICATION_JSON_STREAM)
Flowable<ChannelStatusPackage> packagesStatus(stringParm) {
return ClientStatus.fetchPackages(genesis);
}
}
The java object POJOs:
#Introspected
public class NetworkListPackage {
private List<NetworkList> networkList = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
public List<NetworkList> getNetworkList() {
return networkList;
}
public void setNetworkList(List<NetworkList> networkList) {
this.networkList = networkList;
}
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
public class NetworkList {
private String name;
private Boolean authEnabled;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getAuthEnabled() {
return authEnabled;
}
public void setAuthEnabled(Boolean authEnabled) {
this.authEnabled = authEnabled;
}
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
#Introspected
public class ChannelStatusPackage {
private String chaincodeCount;
private String txCount;
private String latestBlock;
private String peerCount;
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
public String getChaincodeCount() {
return chaincodeCount;
}
public void setChaincodeCount(String chaincodeCount) {
this.chaincodeCount = chaincodeCount;
}
public String getTxCount() {
return txCount;
}
public void setTxCount(String txCount) {
this.txCount = txCount;
}
public String getLatestBlock() {
return latestBlock;
}
public void setLatestBlock(String latestBlock) {
this.latestBlock = latestBlock;
}
public String getPeerCount() {
return peerCount;
}
public void setPeerCount(String peerCount) {
this.peerCount = peerCount;
}
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
And the potential tests:
#MicronautTest
class MyControllerTest {
#Inject
#Client("/")
RxStreamingHttpClient client;
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
//when:
HttpRequest request = HttpRequest.GET("/api/v1/channels/{stringParam}/status/");
Flowable<ChannelStatusPackage> channelStatusPackageStream = client.jsonStream(request, ChannelStatusPackage.class);
Iterable<ChannelStatusPackage> channelStatusPackages = channelStatusPackageStream.blockingIterable();
//then:
//How to assert the returned body compared to the POJO?
//How to handle the parameter in the request url?
#Test
public void verifyNetworkListPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
//when:
HttpRequest request = HttpRequest.GET("/api/v1/networkList");
Flowable<NetworkListPackage> networkListPackageStream = client.jsonStream(request, NetworkListPackage.class);
Iterable<NetworkListPackage> networkListPackages = networkListPackageStream.blockingIterable();
//then:
//How to assert the returned body and compared to the POJO?
//How to assert the returned properties ?
}
}
Based on the previous code, how can I test that the returned bodies and properties of the requests matches the POJOs?
What are the usual test to be carried out?
Thank you very much for helping.
Normaly, the basic assertion start by testing the object type, so this should validate your schema.
An other way to test it is to use RestAssured, witch is a bit more readable.
You need to import the fallowing dependencies in you build.gradle
testImplementation("io.rest-assured:rest-assured:4.2.+")
testImplementation("io.rest-assured:json-schema-validator:4.2.+")
You need test annotation processor to enable micronaut injection and junit 5 for the BeforeEach.
The full test dependencies:
testAnnotationProcessor("io.micronaut:micronaut-inject-java")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("io.micronaut.test:micronaut-test-junit5")
testImplementation("io.rest-assured:rest-assured:4.2.+")
testImplementation("io.rest-assured:json-schema-validator:4.2.+")
testRuntime("org.junit.jupiter:junit-jupiter-engine")
Then you can wright your tests like that:
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import io.micronaut.http.HttpStatus;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.annotation.MicronautTest;
import io.restassured.RestAssured;
import javax.inject.Inject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
#MicronautTest
class MyControllerTest {
#Inject
private EmbeddedServer embeddedServer;
#BeforeEach
public void setUp() {
RestAssured.port = embeddedServer.getPort();
}
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.pathParam("stringParam", "value")
.get("/api/v1/channels/{stringParam}/status/")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(
"chaincodeCount", equalTo("chaincodeCountValue"),
"txCount", equalTo("txCountValue"),
"latestBlock", equalTo("latestBlockValue"),
"peerCount", equalTo("peerCountValue"),
"additionalProperties.key1", equalTo("additionalPropertyValue1"),
"additionalProperties.key2", equalTo("additionalPropertyValue2")
);
}
#Test
public void verifyNetworkListPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.get("/api/v1/networkList")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(
"networkList.name[0]", equalTo("nameValue0"),
"networkList.authEnabled[0]", equalTo("authEnabledValue0"),
"networkList.additionalProperties[0].key1", equalTo("additionalPropertiesValue1"),
"networkList.additionalProperties[0].key2", equalTo("additionalPropertyValue2")
);
}
}
This is not really the way you wanted to do your tests, but I hope it will help.
So I ended up using the "hasItems" matcher or/and the jackson schema matcher.
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import io.micronaut.http.HttpStatus;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.annotation.MicronautTest;
import io.restassured.RestAssured;
import javax.inject.Inject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.hasItems;
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
#MicronautTest
class MyControllerTest {
#Inject
private EmbeddedServer embeddedServer;
#BeforeEach
public void setUp() {
RestAssured.port = embeddedServer.getPort();
}
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.pathParam("stringParam", "value")
.get("/api/v1/channels/{stringParam}/status/")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(matchesJsonSchemaInClasspath("channelsStatus.json"))
.body("keySet()",hasItems(
"chaincodeCount",
"txCount",
"latestBlock",
"peerCount",
);
}
#Test
public void verifyNetworkListPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
given()
.when()
.get("/api/v1/networkList")
.then()
.statusCode(HttpStatus.OK.getCode())
.body(matchesJsonSchemaInClasspath("networkList.json"))
.body("networkList.keySet()",hasItems(
"name",
"authEnabled",
);
}
}
``
Another option is to use jsonPath similar to Spring Boot MockMvc ResultMatcher:
testImplementation 'com.jayway.jsonpath:json-path:2.4.0'
testImplementation 'org.hamcrest:hamcrest:2.2'
Get the response as HttpResponse<String> and then use JsonPath.parse(response.body()) to assert the json path:
#Test
public void verifyChannelStatusPackagesCanBeFetchedWithCompileTimeAutoGeneratedAtClient() {
URI uri = UriBuilder.of("/api/v1/channels/{stringParam}/status/").expand(singletonMap("stringParam", "value"));
HttpResponse<String> response = client.toBlocking().exchange(HttpRequest.GET(uri), String.class);
assertEquals(HttpStatus.OK, response.getStatus());
ReadContext ctx = JsonPath.parse(response.body());
assertThat(ctx.read("$"), isA(Object.class));
assertThat(ctx.read("$.chaincodeCount"), is("chaincodeCountValue"));
}
Example for an endpoint test using Micronaut vs Spring Boot
I want to intercept method named methodA with one arg which's type is String as blow, what should i do. How to use hasParameters() api?
public class Demo {
public static void main(String[] args) {
new ByteBuddy()
.subclass(A.class)
.method(named("methodA").and(hasParameters(?)))
}
static class A {
public void methodA() {
System.out.println("methodA() invoked.");
}
public void methodA(String arg) {
System.out.println("methodA(" + arg + ") invoked.");
}
}
}
For this you want the ElementMatchers.takesArguments(String.class) matcher.
So something like that:
Class<? extends A> loaded = new ByteBuddy().subclass(A.class)
.method(ElementMatchers.named("methodA").and(ElementMatchers.takesArguments(String.class)))
.intercept(MethodDelegation.to(Demo.class))
.make().load(Demo.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION).getLoaded();
A instance = loaded.getConstructor().newInstance();
instance.methodA("abc");
instance.methodA();
public class Demo {
static void intercept(String arg){
System.out.println("intercepted");
}
}
To clarify, you need to define a matcher (similar to a filter) to apply to methods. Create some constraint in the matcher so it will only match to some parameter structure you specify:
ElementMatcher<Iterable<? extends ParameterDescription>> matcher = parameterDescriptions -> {
for (ParameterDescription p : parameterDescriptions) {
if (p.getType().equals(TypeDescription.STRING.asGenericType()) && p.getIndex() == 0) return true;
}
return false;
};
ByteBuddyAgent.install();
new ByteBuddy()
.redefine(SomeType.class)
.method(ElementMatchers.hasParameters(matcher))
.intercept(FixedValue.value("Hello World"))
.make()
.load(SomeType.class.getClassLoader(),
ClassReloadingStrategy.fromInstalledAgent());
I have a JAVA class that has two methods. The first one is the main method and the second one is method1().
Let's say the following is the class:
public class SomeClass() {
public static void main(String[] args) {
SomeClass myObj = new SomeClass();
Map<String,Object> map = new HashMap<String,Object>();
map.put("obj", myObj);
MVEL.eval("System.out.println(\"I am inside main method\");obj.method1();",map);
}
public static void method1(List<String> listOfStrings){
System.out.println("I am inside method 1");
}
}
Now as you can see in the expression, to call method1, I need to pass a list as arguments. How to do that? What changes are required in the expression? What if I want to pass dynamic arguments in my program?
You can create a List or have it coming from some other source as an argument.
Only thing you need to take care is to put inside the map object,
which used by MVEL for evaluation.
Need to pass list as mentioned -> obj.method1(myList);
Working Code Below
public class SomeClass {
public static void main(String[] args) {
SomeClass myObj = new SomeClass();
Map<String, Object> map = new HashMap<String, Object>();
map.put("obj", myObj);
List<String> listOfStrings = new ArrayList<String>();
listOfStrings.add("my ");
listOfStrings.add("List ");
listOfStrings.add("is printing");
map.put("obj", myObj);
map.put("myList", listOfStrings);
MVEL.eval("System.out.println(\"I am inside main method\");obj.method1(myList);",map);
}
public static void method1(List<String> listOfStrings) {
System.out.println("I am inside method 1");
for (String s : listOfStrings) {
System.out.print(s);
}
}
}
output
I am inside main method
I am inside method 1
my List is printing
I have a simple class that I want to deserialize into JSON using Jackson. I want to rename one field logically in my JSON and the other I want to have the same name as defined in my Java class.
#JsonSerialize(include = Inclusion.NON_NULL)
public static class Manifest {
public Manifest(){
this.files = new ArrayList<String>();
}
#JsonProperty("manifest-version")
private String manifestVersion;
private ArrayList<String> files;
#JsonIgnore
public String getManifestVersion() {
return manifestVersion;
}
#JsonIgnore
public void setManifestVersion(String manifestVersion) {
this.manifestVersion = manifestVersion;
}
public ArrayList<String> getFiles() {
return files;
}
public void setFiles(ArrayList<String> files) {
this.files = files;
}
public void addFile(String file) {
this.files.add(file);
}
}
I'm expecting the #JsonIgnore for the getter/setter to cause manifestVersion to not become a JSON property (But should create a JSON property for manifest-version, where I have the #JsonProperty defined.
Expected output is
{
"manifest-version" : "2.0"
}
Actual output is
{
"manifest-version" : "2.0",
"manifestVersion":"2.0"
}
Any help would be appreciated.
I tried executing your code with Jackson 2.2 and i'm getting the expected output
import java.util.ArrayList;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize.Inclusion;
public class Test {
#JsonSerialize(include = Inclusion.NON_NULL)
public static class Manifest {
public Manifest(){
this.files = new ArrayList<String>();
}
#JsonProperty("manifest-version")
private String manifestVersion;
private ArrayList<String> files;
#JsonIgnore
public String getManifestVersion() {
return manifestVersion;
}
#JsonIgnore
public void setManifestVersion(String manifestVersion) {
this.manifestVersion = manifestVersion;
}
public ArrayList<String> getFiles() {
return files;
}
public void setFiles(ArrayList<String> files) {
this.files = files;
}
public void addFile(String file) {
this.files.add(file);
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper obj = new ObjectMapper();
Manifest m = new Manifest();
m.setManifestVersion("2.0");
System.out.println(obj.writeValueAsString(m));
}
}
Output: {"files":[],"manifest-version":"2.0"}
what version of jackson are you using?
My goal is to be able to override what I get back from CustomClass.class.getName() and CustomClass.getClass().getName()
It should return a custom value, which I think is best to define in an attribute like
#NameOverride("Custom.fully.qualified.class.name")
public class CustomClass {}
Is there any way to do that?
Fred's answer is okay, but his aspect could be somewhat more elegant with less code and especially fewer reflection calls. Sorry, I prefer AspectJ native code style, but #AspectJ annotation style would not be much longer:
String around(Class clazz) : call(public String Class.getName()) && target(clazz) {
NameOverride nameOverride = (NameOverride) clazz.getAnnotation(NameOverride.class);
return nameOverride == null ? proceed(clazz) : nameOverride.value();
}
Here is the full source code. I added a class without annotation to show the different behaviour and also a restriction to class definitions - #Target(ElementType.TYPE) - to the annotation class. I am also showing package names and imports:
package test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface NameOverride {
String value();
}
package test;
public class NormalClass {}
package test;
#NameOverride("Custom.fully.qualified.class.name")
public class CustomClass {}
package test;
public class Main {
public static void main(String[] args) {
System.out.println(NormalClass.class.getName());
System.out.println(CustomClass.class.getName());
System.out.println(new NormalClass().getClass().getName());
System.out.println(new CustomClass().getClass().getName());
}
}
package aspectj;
import test.NameOverride;
public aspect GetNameOverrider {
#SuppressWarnings({ "unchecked", "rawtypes" })
String around(Class clazz) : call(public String Class.getName()) && target(clazz) {
NameOverride nameOverride = (NameOverride) clazz.getAnnotation(NameOverride.class);
return nameOverride == null ? proceed(clazz) : nameOverride.value();
}
}
The output:
test.NormalClass
Custom.fully.qualified.class.name
test.NormalClass
Custom.fully.qualified.class.name
This is for sure not the best/fastest solution but maybe a POC...
First of all the file structure:
./src/aspectj:
GetNameOverrider.aj
./src/test:
CustomClass.java Main.java NameOverride.java
NameOverride.java:
#Retention(RetentionPolicy.RUNTIME)
public #interface NameOverride {
String value();
}
GetNameOverrider.aj:
#Aspect
public class GetNameOverrider {
#Around("call(String getName()) && !within(aspectj..*)")
public String advice(ProceedingJoinPoint pjp) throws Throwable {
String ret = (String) pjp.proceed();
String className = "" + pjp.getTarget();
className = className.replace("class ", "");
try {
test.NameOverride anno = Class.forName(className).getAnnotation(
test.NameOverride.class);
if (anno != null) {
return anno.value();
}
} catch (ClassNotFoundException e) {
return ret;
}
return ret;
}
}
gives me for Main.java:
public class Main {
public static void main(String[] args) {
System.out.println(CustomClass.class.getName());
System.out.println(new CustomClass().getClass().getName());
}
}
the output:
Custom.fully.qualified.class.name
Custom.fully.qualified.class.name