Jersey Client with Gson

It doesn’t make sense to have full test coverage of the jersey-gson project in selenium. Let’s fill in the gaps with some jersey-client unit tests.

First, we need a UserMap deserializer to mirror the existing serializer. The existing serializer sends the UserMap as an object whose value is an Array of rows (this simplifies interaction with the DataTables front end). We deserialize the UserMap by iterating through the array, turning each row into a User object, and adding each User object to the Map.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
public class UserMapUnmarshall implements JsonDeserializer<UserMap> {
private static Gson gson = new Gson();

@Override
public UserMap deserialize(JsonElement mapFormat, Type typeOfSrc, JsonDeserializationContext context) {
Iterator<JsonElement> iterate = mapFormat.getAsJsonObject()
.get("data")
.getAsJsonArray()
.iterator();

UserMap users = new UserMap();
while( iterate.hasNext() ) {
JsonObject user = ((JsonElement) iterate.next()).getAsJsonObject();
String email = user.get("email").getAsString();
User add = new User();
add.setEmail( email );
add.setSurname( user.get("surname").getAsString() );
add.setGivenName( user.get("givenName").getAsString() );
users.put( email, add );
}

return users;
}
}

Next, we need to register the new UserMapUnmarshall class for use by the GsonReader class - just as we registered the UserMapMarshall class for the GsonWriter class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
diff --git a/src/main/java/com/ideoplex/tutorial/GsonReader.java b/src/main/java/com/ideoplex/tutorial/GsonReader.java
index 144fbde..4e117cc 100644
--- a/src/main/java/com/ideoplex/tutorial/GsonReader.java
+++ b/src/main/java/com/ideoplex/tutorial/GsonReader.java
@@ -28,12 +28,16 @@ import javax.ws.rs.core.MultivaluedMap;


import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;


@Provider
@Consumes(MediaType.APPLICATION_JSON)
@Singleton
public class GsonReader<T> implements MessageBodyReader<T> {
+ protected static Gson gson = new GsonBuilder()
+ .registerTypeAdapter(UserMap.class,new UserMapUnmarshall())
+ .create();

@Override
public boolean isReadable(Class<?> type, Type genericType,
@@ -46,8 +50,7 @@ public class GsonReader<T> implements MessageBodyReader<T> {
Annotation[] antns, MediaType mt,
MultivaluedMap<String, String> mm, InputStream in)

throws IOException, WebApplicationException {

- Gson g = new Gson();
- return g.fromJson(_convertStreamToString(in), type);
+ return gson.fromJson(_convertStreamToString(in), type);
}

private String _convertStreamToString(InputStream inputStream)

Let’s complete our suite of server services by adding a method to get a single user and a method to delete a user. The new feature in this code is the use of the PathParam annotation to extract values from the request path. And the remainder of the new code builds on features that we’ve used before.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
diff --git a/src/main/java/com/ideoplex/tutorial/MyResource.java b/src/main/java/com/ideoplex/tutorial/MyResource.java
index a69c8ac..6ef69fb 100644
--- a/src/main/java/com/ideoplex/tutorial/MyResource.java
+++ b/src/main/java/com/ideoplex/tutorial/MyResource.java
@@ -1,8 +1,10 @@
package com.ideoplex.tutorial;

+import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
@@ -61,6 +63,32 @@ public class MyResource {
return user;
}

+ @GET
+ @Path("user/get/{email}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public User getUser(@PathParam("email") String email) {
+ User rVal = users.get(email);
+ if ( rVal == null ) {
+ throw new WebApplicationException(Response
+ .status(Response.Status.NOT_FOUND)
+ .build());
+ }
+ return rVal;
+ }
+
+ @DELETE
+ @Path("user/delete/{email}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public User deleteUser(@PathParam("email") String email) {
+ User rVal = users.remove(email);
+ if ( rVal == null ) {
+ throw new WebApplicationException(Response
+ .status(Response.Status.NOT_FOUND)
+ .build());
+ }
+ return rVal;
+ }
+
@POST
@Path("user/post")
@Consumes(MediaType.APPLICATION_JSON)

Now we can add a set of jersey client unit tests to exercise our server methods. I’ve split the code into multiple blocks for for clarity. The first block of code registers the GsonReader and GsonWriter classes for use by the client (explicit registration is not needed on the server because the automated server scan searches for the annotated classes).

1
2
3
4
5
6
7
8
9
10
11
Client          client;
WebResource resource;
ClientResponse response;

{
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(GsonReader.class);
config.getClasses().add(GsonWriter.class);

client = Client.create(config);
}

Execute a GET with an email path parameter:

1
2
3
4
5
System.out.println("Verify that " + email + " is not available yet");
resource = client.resource( url + "/get/" + email );
response = resource.accept(MediaType.APPLICATION_JSON)
.get(ClientResponse.class);
assert( response.getStatus() == 404 );

Execute a POST, submitting a User object and parsing the User object from the response.

1
2
3
4
5
6
7
8
System.out.println("Verify that user/post on " + email + " succeeds");
resource = client.resource( url + "/post" );
response = resource.accept(MediaType.APPLICATION_JSON)
.type(MediaType.APPLICATION_JSON)
.post(ClientResponse.class,sample);
assert( response.getStatus() == 200 );
User rVal = response.getEntity(User.class);
assert( email.equals(rVal.getEmail()) );

Execute a GET, parsing the UserMap object from the response.

1
2
3
4
5
6
7
8
9
System.out.println("Verify that " + email + " is in the userMap");
resource = client.resource( url + "/map" );
response = resource.accept(MediaType.APPLICATION_JSON)
.get(ClientResponse.class);
assert( response.getStatus() == 200 );
UserMap rVal = response.getEntity(UserMap.class);
assert( rVal != null );
User look = rVal.get(email);
assert( look != null );

Execute a DELETE, parsing the User object from the response.

1
2
3
4
5
6
7
System.out.println("Verify that delete on " + email + " succeeds");
resource = client.resource( url + "/delete/" + email );
response = resource.accept(MediaType.APPLICATION_JSON)
.delete(ClientResponse.class);
assert( response.getStatus() == 200 );
User rVal = response.getEntity(User.class);
assert( email.equals(rVal.getEmail()) );

And finally, a small change to the BrowserTest test annotation to insure out new client tests run before the selenium tests.

1
2
3
4
5
6
7
8
9
10
11
12
13
diff --git a/src/test/java/com/ideoplex/tutorial/BrowserTest.java b/src/test/java/com/ideoplex/tutorial/BrowserTest.java
index bac07fb..f70c161 100644
--- a/src/test/java/com/ideoplex/tutorial/BrowserTest.java
+++ b/src/test/java/com/ideoplex/tutorial/BrowserTest.java
@@ -76,7 +76,7 @@ public class BrowserTest {
}

@Parameters({"browser","baseurl","waitajax"})
- @Test(invocationCount = 2, groups="browser")
+ @Test(invocationCount = 2, dependsOnGroups="client", groups="browser")
public void userCreate( String browser, String baseurl, String waitajax )
{

WebDriver driver = "chrome".equalsIgnoreCase(browser)

The full source for this version of Jersey, Gson and Datatables is on github. BTW, I’m using jersey-client 1.18 for expediency, I’ll update this for 2.x in the near future.

31 Oct jersey-gson Content-Type Woes
03 Nov Cucumber-java and TestNG