Skip to content

Instantly share code, notes, and snippets.

@abhinavguptas
Created September 24, 2011 03:05
Show Gist options
  • Save abhinavguptas/1238904 to your computer and use it in GitHub Desktop.
Save abhinavguptas/1238904 to your computer and use it in GitHub Desktop.
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