Automatically JUnit Test DTO and Transfer Objects
February 16th, 2016
Automatically JUnit Test DTO and Transfer Objects
Quickstart
Clone the example project from github and run the following commands:
`./gradlew bootRun
curl http://localhost:8080/users
curl http://localhost:8080/profiles`
Great. What does it do?
This example Spring Boot project, featuring spring-data-rest, demonstrates how you can link a field within an entity (e.g. a value in a table column) to a REST resource, as opposed to another table.
This linking is achieved via the following:
Breaking it down First, some simple data is loaded on startup via a BootstrapData class. This is done via simple spring-data JPA repositories. I’ve highlighted the interesting bits. Note: I’ve been using Groovy (mostly within the context of Grails) almost exclusively for the past 5 years. For that reason, all of this code is in Groovy. It wouldn’t take much effort to rewrite it in Java though.
`void loadData() {
User user = new User(
id: 1L,
username: "opi1",
**userProfile**: "5"
)
userRepository.save(user)
Profile profile = new Profile(
id: Long.parseLong(**user.userProfile**),
firstName: "Hammer",
lastName: "Proper",
**zipCode**: "55413"
)
profileRepository.save(profile)
}`
You can see from this setup code that there is a link between User and Profile. Even though these two domain classes exist within the same app, pretend that they live in different apps and in different databases.
Why is zipCode highlighted? That doesn’t look like it’s linked to anything. We’ll explore that when we look at the classes for these two entities.
The @Entity classes: straight up JPA with a little extra flavor
Let’s start with the User class. Once again, the important pieces are highlighted.
`@Entity
**@EntityWithRestResource**
class User {
@Id
Long id
String username
**@RestResourceMapper(context = RestResourceContext.PROFILE, path="/#id")**
String userProfile
}`
First up is the @EntityWithRestResource annotation. This is simply used as a way to identify this class as needing some additional treatment by the ResourceProcessor, which will be mentioned many times in this article. We don’t want to waste any processing time on regular entity classes that don’t have any mappings to REST resources.
Now for the crux of the topic, the @RestResourceMapper. This annotation indicates the intent that a particular field within an entity is linked to a REST resource. This linking is meant to be read only. In this case, userProfile is linked to some REST resource, which ultimately is a Profile instance in another app. If you wanted to modify that Profile instance, you would do so in the app where it lives. The good news is that the HATEOAS output will tell you exactly where to go if you want to do that. The ResourceProcessor uses the attribute values of the annotation to construct links and/or resolve REST resources.
Let’s look at the @RestResourceMapper annotation in more detail.
`@Target([ElementType.FIELD])
@Retention(RetentionPolicy.RUNTIME)
public @interface RestResourceMapper {
boolean external() default false
RestResourceContext context() default RestResourceContext.LOCAL
String path() default ""
String[] params() default []
String apiKey() default ""
String resolveToProperty() default ""
}`
How are these annotation attributes used?
Let’s take another look at how the @RestResourceMapper annotation is being used by the User class.
`@RestResourceMapper(context = RestResourceContext.PROFILE, path="/#id")
String userProfile`
Only two attributes are specified, the context and path. The ResourceProcessor will use these attribute values, along with the value in the userProfile String to construct a link. We can see what that link looks like by running the following command.
curl **http://localhost:8080/users**
This yields the following output:
`{
"_embedded" : {
"users" : [ {
"username" : "opi1",
"userProfile" : "5",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/1"
},
"user" : {
"href" : "http://localhost:8080/users/1"
},
"userProfile" : {
"href" : "**http://localhost:8080/profiles/5**"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/users"
},
"profile" : {
"href" : "http://localhost:8080/profile/users"
}
},
"page" : {
"size" : 20,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}`
As you can see, the link http://localhost:8080/profiles/5 was constructed by the ResourceProcessor and inserted into the _links portion of the HATEOAS output. So if we break it down, we can see the following:
context = RestResourceContext.PROFILE
path=”/#id”
Before we examine the Profile for this User, let’s look at the Profile class first.
`@Entity
@EntityWithRestResource
class Profile {
@Id
Long id
String firstName
String lastName
@RestResourceMapper(
external = true,
context = RestResourceContext.LOCATOR,
path = "/geocode/json",
params = ["key=#apiKey", "components=postal_code:#id"],
apiKey = RestResourceApiKey.GOOGLE,
resolveToProperty = "location"
)
String zipCode
@Transient
Object location
}`
Now we can see all of the @RestResourceMapper attributes in action. In this case, the ResourceProcessor will do the following:
Let’s take a brief look at how it does this. Note: For clarification, the ResourceProcessor is a Spring HATEOAS interface for processing @Entity types. In this case, we have a generic ResourceProcessor that will be triggered for processing any entity.
`if (resource.content.class.isAnnotationPresent(EntityWithRestResource.class)) {
Map links = [:]
// process any fields that have the RestResourceMapper annotation
resource.content.class.declaredFields.each { Field field ->
RestResourceMapper restResourceMapper = field.getAnnotation(RestResourceMapper.class)
if (restResourceMapper) {
String resourceId = resource.content."${field.name}"
if (resourceId) {
// construct a REST endpoint URL from the annotation properties and resource id
final String resourceURL = restResourceMapperService.getResourceURL(restResourceMapper, resourceId)
// for eager fetching, fetch the resource and embed its contents within the designated property
// no links are added
if (restResourceMapper.resolveToProperty()) {
String resolvedResource = restResourceMapperService.getResolvedResource(resourceURL)
resource.content."${restResourceMapper.resolveToProperty()}" =
ResourceParsingUtil.deserializeJSON(resolvedResource)
} else {
// for external links, we simply want to put the constructed URL into the JSON output
// for internal links, we want to ensure that the URL conforms to HATEOAS for the given resource
links.put(field.name, restResourceMapper.external() ?
resourceURL : restResourceMapperService.getHATEOASURLForResource(resourceURL, resource.content.class))
}
}
}
}
//...
}`
You can see that there’s some Groovy goodness being used to get and set attributes on the entity class with a little more ease. Hopefully you can get the gist of it from this code snippet.
Now, knowing all of that, let’s see the content of the Profile for this User:
curl **http://localhost:8080/profiles/5**
`{
"firstName" : "Hammer",
"lastName" : "Proper",
"zipCode" : "55413",
"location" : {
"error_message" : "The provided API key is invalid.",
"results" : [ ],
"status" : "REQUEST_DENIED"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/profiles/5"
},
"profile" : {
"href" : "http://localhost:8080/profiles/5"
}
}
}`
Notice that an error message is encapsulated within the location field. If we had a valid API key from Google, the output would look like this:
`{
"firstName" : "Hammer",
"lastName" : "Proper",
"zipCode" : "55413",
"location" : {
"results" : [ {
"address_components" : [ {
"long_name" : "55413",
"short_name" : "55413",
"types" : [ "postal_code" ]
}, {
"long_name" : "Minneapolis",
"short_name" : "Minneapolis",
"types" : [ "locality", "political" ]
}, {
"long_name" : "Minnesota",
"short_name" : "MN",
"types" : [ "administrative_area_level_1", "political" ]
}, {
"long_name" : "United States",
"short_name" : "US",
"types" : [ "country", "political" ]
} ],
"formatted_address" : "Minneapolis, MN 55413, USA",
"geometry" : {
"bounds" : {
"northeast" : {
"lat" : 45.01548100000001,
"lng" : -93.2061531
},
"southwest" : {
"lat" : 44.9868921,
"lng" : -93.27577
}
},
"location" : {
"lat" : 44.9956414,
"lng" : -93.258095
},
"location_type" : "APPROXIMATE",
"viewport" : {
"northeast" : {
"lat" : 45.01548100000001,
"lng" : -93.2061531
},
"southwest" : {
"lat" : 44.9868921,
"lng" : -93.27577
}
}
},
"place_id" : "ChIJ3T8206Mts1IRp8YOpjWHU9k",
"types" : [ "postal_code" ]
} ],
"status" : "OK"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/profiles/5"
},
"profile" : {
"href" : "http://localhost:8080/profiles/5"
}
}
}`
And there you have it, links to REST resources within entity mappings. Remember, this is only meant to provide read-only functionality. If you’re interested in doing writes, feel free to fork the project and take a crack at it. That sort of design is questionable though, and the ResourceProcessor is not going to help you at all in that case. Speaking of the ResourceProcessor, it resides as a Bean within a configuration class, RestResourceMapperConfig, so take a look at that if you want to see where the “magic” happens.
That’s all for now. Happy coding!
Automatically JUnit Test DTO and Transfer Objects
Continuous delivery is the very first principle behind the Agile Manifesto, and yet it continues to plague software development teams. In this talk, David Norton will give a brief overview of continuous delivery practices and then focus on the area…
A guide to using Groovy for Spring configuration both JavaConfig and Grails style and adding Groovy configuration to web.xml using ContextLoaderListener.
Insert bio here