Fixture for Apex HTTP Callout Testing [Note: file name kept .java instead of .cls for syntax highlighting :)]
/* | |
Copyright (c) 2011 tgerm.com | |
All rights reserved. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions | |
are met: | |
1. Redistributions of source code must retain the above copyright | |
notice, this list of conditions and the following disclaimer. | |
2. Redistributions in binary form must reproduce the above copyright | |
notice, this list of conditions and the following disclaimer in the | |
documentation and/or other materials provided with the distribution. | |
3. The name of the author may not be used to endorse or promote products | |
derived from this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR | |
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
public with sharing class AWS { | |
/** | |
Handle to easily switch between natural HTTP callout and Mock Callouts for test cases. | |
Those who love patterns, can use Factory if required to achieve the same. But I find | |
it really handy in test cases to change. | |
for ex. My test code can just replace the IHttpCallout impl as: | |
AWS.CALLOUT = new MockAWSCallout(); | |
Here MockAWSCallout is a fake implementation that will simulate HTTP callout for sake of test cases. | |
*/ | |
public static WS.IHttpCallout CALLOUT = new WS.DefHttpCallout(); | |
public String serviceEndPoint = '<SET IT HERE>'; | |
public String bucket = '<SET IT HERE>'; | |
public String key = '<SET IT HERE>'; | |
/* | |
Example AWS callout code taken from this awesome WIKI page : | |
http://wiki.developerforce.com/index.php/Apex_Web_Services_and_Callouts | |
Please Note: this method is just for illustration purpose, not ready to be used as it is. | |
*/ | |
public void store(String body) { | |
HttpRequest req = new HttpRequest(); | |
//Set HTTPRequest Method | |
req.setMethod('PUT'); | |
//Set HTTPRequest header properties | |
req.setHeader('content-type', 'image/gif'); | |
req.setHeader('Content-Length','1024'); | |
req.setHeader('Host','s3.amazonaws.com'); | |
req.setHeader('Connection','keep-alive'); | |
req.setEndpoint( this.serviceEndPoint + this.bucket +'/' + this.key); | |
//Set the HTTPRequest body | |
req.setBody(body); | |
try { | |
//Execute web service call here | |
// | |
// PLEASE NOTE : here we have used the static variable | |
// CALLOUT here | |
// | |
WS.IHttpResponse res = CALLOUT.send(req); | |
//Helpful debug messages | |
System.debug('STATUS:'+res.getStatus()); | |
System.debug('STATUS_CODE:'+res.getStatusCode()); | |
/// Do what else is biz requirement with the response. | |
// ... | |
//..... | |
// | |
} catch(System.CalloutException e) { | |
//Exception handling goes here.... | |
} | |
} | |
} |
/* | |
Copyright (c) 2011 tgerm.com | |
All rights reserved. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions | |
are met: | |
1. Redistributions of source code must retain the above copyright | |
notice, this list of conditions and the following disclaimer. | |
2. Redistributions in binary form must reproduce the above copyright | |
notice, this list of conditions and the following disclaimer in the | |
documentation and/or other materials provided with the distribution. | |
3. The name of the author may not be used to endorse or promote products | |
derived from this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR | |
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
@isTest | |
private class Test_AWS { | |
/** | |
Please note we have extended WS.MockHttpResponseBase | |
instead of implementing ws.IHttpResponse, because we want to | |
override three methods only | |
*/ | |
public class MockHttpResponse extends WS.MockHttpResponseBase { | |
public String body; | |
public String status; | |
public Integer statusCode; | |
public MockHttpResponse(String body, String status, Integer statusCode) { | |
this.body = body; | |
this.status = status; | |
this.statusCode = statusCode; | |
} | |
public override String getBody() { | |
return body; | |
} | |
public override String getStatus() { | |
return status; | |
} | |
public override Integer getStatusCode() { | |
return statusCode; | |
} | |
} | |
/** | |
Mock Callout Implementation | |
*/ | |
public class MockHttpCallout implements WS.IHttpCallout { | |
private MockHttpResponse resp; | |
public WS.IHttpResponse send(HttpRequest req) { | |
return resp; | |
} | |
/** | |
This method was not part of original WS.IHttpCallout contract | |
as its one of the way test case can pass mock response to it. | |
*/ | |
public void setResponse(MockHttpResponse resp) { | |
this.resp = resp; | |
} | |
} | |
/* | |
A test call to the store method in AWS class | |
*/ | |
static testMethod void testStoreCall() { | |
MockHttpCallout mockCallout = new MockHttpCallout(); | |
// Tell AWS Apex class to use Mock Callout instead of this one | |
AWS.CALLOUT = mockCallout; | |
AWS amazon = new AWS(); | |
// create a mock XML response you want the actual code to parse | |
MockHttpResponse mockResp = new MockHttpResponse('<xml..> some xml response body', 'OK', 200); | |
// tell callout to return this response when a request comes | |
mockCallout.setResponse(mockResp); | |
amazon.store('My Cool Body to preserve in Amazon S3 :)'); | |
// Please do some assertions | |
// | |
System.assertEquals('Some Good Asserts here', 'No good asserts, writing those will be out of scope for this illustration'); | |
// | |
} | |
} |
/* | |
Copyright (c) 2011 tgerm.com | |
All rights reserved. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions | |
are met: | |
1. Redistributions of source code must retain the above copyright | |
notice, this list of conditions and the following disclaimer. | |
2. Redistributions in binary form must reproduce the above copyright | |
notice, this list of conditions and the following disclaimer in the | |
documentation and/or other materials provided with the distribution. | |
3. The name of the author may not be used to endorse or promote products | |
derived from this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR | |
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
/** | |
Collection of classes/interfaces for creating a fixture that will ease the Testing effort with webservices. | |
*/ | |
public with sharing class WS { | |
/** | |
Contract for HTTPResponse. To avoid learning and confusions this interface is exposing | |
all the methods available in Apex HTTPResponse class(http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_restful_http_httpresponse.htm) | |
Test classes can override and implement them as required | |
*/ | |
public interface IHttpResponse { | |
String getBody(); | |
Dom.Document getBodyDocument(); | |
String getHeader(String key); | |
String[] getHeaderKeys(); | |
String getStatus(); | |
Integer getStatusCode(); | |
Xmlstreamreader getXmlStreamReader(); | |
// have to name it toStrings() instead of toString(), as the later | |
// is reserved by Apex | |
String toStrings(); | |
} | |
/** | |
Contract for a simple web service callout using Apex. | |
Only a single method is available for abstracting the stuff out for ease of Testing. | |
Test classes can provide implmentations of this interface to return custom/fake/mock HTTP responses. | |
*/ | |
public interface IHttpCallout { | |
/** | |
Accepts a ready to send requests and makes a callout using that. | |
*/ | |
IHttpResponse send(HttpRequest req); | |
} | |
/** | |
Default wrapper implementation over standard Apex HttpResponse class. | |
Reference : http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_restful_http_httpresponse.htm | |
All contract methods of IHttpResponse are delegated to the wrapped Apex HttpResponse instance. | |
*/ | |
public virtual class DefHttpResponse implements IHttpResponse { | |
public Httpresponse resp; | |
public DefHttpResponse(HttpResponse resp) { | |
this.resp = resp; | |
} | |
public String getBody() { | |
return resp.getBody(); | |
} | |
public Dom.Document getBodyDocument() { | |
return resp.getBodyDocument(); | |
} | |
public String getHeader(String key) { | |
return resp.getHeader(key); | |
} | |
public String[] getHeaderKeys() { | |
return resp.getHeaderKeys(); | |
} | |
public String getStatus() { | |
return resp.getStatus(); | |
} | |
public Integer getStatusCode() { | |
return resp.getStatusCode(); | |
} | |
public Xmlstreamreader getXmlStreamReader() { | |
return resp.getXmlStreamReader(); | |
} | |
public String toStrings() { | |
return resp.toString(); | |
} | |
} | |
/** | |
Default implementation meant for use in actual apex code. It runs out of | |
standard Apex Http Class (http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_restful_http_http.htm) | |
*/ | |
public class DefHttpCallout implements IHttpCallout { | |
public IHttpResponse send(HttpRequest req) { | |
Http http = new Http(); | |
return new DefHttpResponse(http.send(req)); | |
} | |
} | |
/** | |
Indicator for operation being accessed is virtual as of now. | |
*/ | |
public class VirtualOperationException extends Exception {} | |
/** | |
Meant to be base/parent class for Apex test case simulations. | |
Its a mock implementation of IHttpResponse, it gives a virtual body | |
of all contract methods in IHttpResponse, and throws VirtualOperationException | |
for every method call. Using this class as parent, subclasses would be easy i.e. just | |
override the methods required, instead of implementing the whole IHttpResponse contract. | |
For ex. in most of the cases, one will override getStatusCode() and getBody() for testing purposes. | |
*/ | |
public virtual class MockHttpResponseBase implements IHttpResponse { | |
public virtual String getBody() { | |
throw new VirtualOperationException('No implementation available !'); | |
} | |
public Dom.Document getBodyDocument() { | |
throw new VirtualOperationException('No implementation available !'); | |
} | |
public virtual String getHeader(String key) { | |
throw new VirtualOperationException('No implementation available !'); | |
} | |
public virtual String[] getHeaderKeys() { | |
throw new VirtualOperationException('No implementation available !'); | |
} | |
public virtual String getStatus() { | |
throw new VirtualOperationException('No implementation available !'); | |
} | |
public virtual Integer getStatusCode() { | |
throw new VirtualOperationException('No implementation available !'); | |
} | |
public virtual Xmlstreamreader getXmlStreamReader() { | |
throw new VirtualOperationException('No implementation available !'); | |
} | |
public virtual String toStrings() { | |
throw new VirtualOperationException('No implementation available !'); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment