The RESTClient class provides methods to perform the HTTP's GET, POST, PUT and DELETE methods. The RESTClient instance can be constructed with a url string and optionally a content type. The HTTP methods can then be called with a named parameter list. For example, the below code will create a RESTClient instance and call the get method passing in the path and a path parameter:
def client = new RESTClient("http://localhost:8080/testWebService/") def resp = client.get(path : "server/status/ServerOne")
The response returned is an instance of HttpResponseDecorator. This wraps the HttpResponse to allow for easy access to the data which is parsed depending upon the content type. The below class shows it's use within a Spock framework test and how the parsed data can be extracted from the response object. The returned XML is:
<serverstatus> <servername>ServerOne</servername> <isrunning>true</isrunning> </serverstatus>
then
<serverstatus> <servername>ServerTwo</servername> <isrunning>false</isrunning> </serverstatus>
import groovy.util.slurpersupport.GPathResult import groovyx.net.http.RESTClient import spock.lang.* import groovyx.net.http.ContentType class InjectedRestServiceTest extends Specification { @RESTWebClient def client def "Test Server Statuses"() { when: "retrieve server status" def resp1 = client.get(path : "server/status/ServerOne") def resp2 = client.get(path : "server/status/ServerTwo") then: "test server one response" assert resp1.data.serverName.text() == "ServerOne" assert resp1.data.isRunning.text() == "true" then: "test server two response" assert resp2.data.serverName.text() == "ServerTwo" assert resp2.data.isRunning.text() == "false" } }
The test asserts are purely there in this example to prove that the response from the real web service are the same as from the mocked web service. In reality, you would be testing some other functionality of which calling a RESTful web service is part of the flow. You wouldn't really assert the response from a mocked RESTClient although you may want to verify that it has been called n times.
The InjectedRestServiceTest class has the RESTClient injected using the custom made annotation @RESTWebClient. There are three classes involved which enable this functionality. First the interface, which is marked as only visible at field level, contains an @ExtensionAnnotation specifying the class which basically tells Spock what to do when it encounters the @RESTClient annotation:
import java.lang.annotation.ElementType import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy import java.lang.annotation.Target import org.spockframework.runtime.extension.ExtensionAnnotation @Retention(RetentionPolicy.RUNTIME) @Target([ ElementType.FIELD ]) @ExtensionAnnotation(RESTWebClientAnnotationDrivenExtension) public @interface RESTWebClient{}
import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension import org.spockframework.runtime.extension.AbstractMethodInterceptor import org.spockframework.runtime.extension.IMethodInvocation import org.spockframework.runtime.model.FieldInfo import org.spockframework.runtime.model.SpecInfo class RESTWebClientAnnotationDrivenExtension extends AbstractAnnotationDrivenExtension<RESTWebClient> { private static final String url = System.getProperties().getProperty("url") private static final boolean useMock = Boolean.parseBoolean(System.getProperties().getProperty("useMock")) @Override void visitFieldAnnotation(RESTWebClient annotation, FieldInfo field) { def methodInterceptor = new RESTWebClientMethodInterceptor(field, url, useMock) methodInterceptor.install(field.parent) } }
The above extension tells Spock that when it encounters @RESTClient, it must create an interceptor with the arguments denoting the field to which the RESTClient instance is to be set, the url of the web service and a boolean to specify if the mock is to be used or the real web service should be used. (The meaning of the two values could be combined to just have a url and if set then the real web service must be used.)
The interceptor will create either a mock RESTClient or a real one. It will do this when the client field needs to be set in the InjectedRestServiceTest class. If the real RESTClient is required then an instance using the url will be created and set using the fieldInfo.writeValue method. If a mock is required then a Mockito mock RESTClient is created and the expected behaviour mocked using instances of the class HttpResponseDecorator (although these could be mocks too.) Again, this is set using the fieldInfo.writeValue method. The mocking code should probably be in its own method or even class but for this simple example its within the setupRESTClient method:
import groovy.mock.interceptor.MockFor import groovyx.net.http.HttpResponseDecorator import groovyx.net.http.RESTClient import org.spockframework.runtime.extension.AbstractMethodInterceptor; import org.spockframework.runtime.extension.IMethodInvocation; import org.spockframework.runtime.model.FieldInfo; import org.spockframework.runtime.model.SpecInfo; import static org.mockito.Mockito.* import static org.mockito.Matchers.* class RESTWebClientMethodInterceptor extends AbstractMethodInterceptor { private final FieldInfo fieldInfo private final String url private final boolean useMock RESTWebClientMethodInterceptor(FieldInfo fieldInfo, String url, boolean useMock) { this.fieldInfo = fieldInfo this.url = url this.useMock = useMock } @Override void interceptSetupMethod(IMethodInvocation methodInvocation) { setupRESTClient(methodInvocation.target) methodInvocation.proceed() } @Override void install(SpecInfo specInfo) { specInfo.setupMethod.addInterceptor this } private void setupRESTClient(target) { if (useMock) { def xmlServerOne = """ <serverStatus> <serverName>ServerOne</serverName> <isRunning>true</isRunning> </serverStatus> """ def xmlServerTwo = """ <serverStatus> <serverName>ServerTwo</serverName> <isRunning>false</isRunning> </serverStatus> """ def httpResponseDecoratorServerOne = new HttpResponseDecorator( null, new XmlParser().parseText(xmlServerOne)) def httpResponseDecoratorServerTwo = new HttpResponseDecorator( null, new XmlParser().parseText(xmlServerTwo)) def mapServerOne = [path:"server/status/ServerOne"] def mapServerTwo = [path:"server/status/ServerTwo"] RESTClient mockRESTClient = org.mockito.Mockito.mock(RESTClient) when(mockRESTClient.get(mapServerOne)).thenReturn(httpResponseDecoratorServerOne); when(mockRESTClient.get(mapServerTwo)).thenReturn(httpResponseDecoratorServerTwo); fieldInfo.writeValue(target, mockRESTClient) } else { fieldInfo.writeValue(target, new RESTClient(url)) } } }
Whilst there are different ways to mock and test RESTful web services, the above classes do show how easily RESTful web services can be called and their responses parsed, how annotations can be created and how objects can be mocked. Similar examples could be constructed for different aspects of a Spock test eg features, fixtures etc..
More can be found on extensions at http://code.google.com/p/spock/wiki/SpockBasics#Extensions and on RESTClient at http://groovy.codehaus.org/modules/http-builder/doc/rest.html
No comments:
Post a Comment
Note: only a member of this blog may post a comment.