Enhanced Web Service Testing
​ Kirk Steffke
​ AppExchange Practice Lead
​ kirk@crmscience.com
​ @kirkevonphilly
​ 
A Better...
•  Test Coverage Overview
•  Review of Native Testing Methods
•  WebServiceMock and HttpCalloutMock
Interfaces
•  StaticRe...
Test Coverage Overview
The who, what, when, where, and why’s of creating coverage
•  Code we write…
•  …about the code we just wrote
•  Test classes
•  Independent of code being tested
•  One or more test...
Apex Test Coverage
​ A Bit More of “How”
Grandma’s Famous Recipe
1.  Write code to do
something
2.  Create test class
3.  ...
•  Code we write…
•  …about the code we just wrote
•  Test classes
•  Independent of code being tested
•  One or more test...
•  You, me, everyone
•  Required
•  75%
•  Ongoing Development
•  Help catch regressions
•  Positive and negative assertio...
•  As you’re developing
•  Moving code from sandbox to production
•  Creating packages
Apex Test Coverage
​ When
Native Testing Strategies
The Tools that are Already in your Toolbox
•  Class w/ single method that makes a callout
Sample Scenario: Single Callout
​ Class Needing Coverage
Sample Scenario: Single Callout
​ Class Needing Coverage
​ Gist: https://gist.github.com/KirkSteffke/2ca4357c7e84eece9073
•  Method making callout needs coverage
•  Don’t want test methods to actually make callout!
•  Responses to be used for p...
•  Defined within test class
•  Returns single callout
response
•  WebServiceMock for WSDL
based SOAP callouts
•  HttpCallo...
•  Defined within test class
•  Returns single callout
response
•  WebServiceMock for WSDL
based SOAP callouts
•  HttpCallo...
•  Not “the” test, but is @test
•  Called by test class methods
•  Returns “mock” responses
•  Sets up header, body, and s...
HttpCalloutMock Interface
​ Part 1: Mock Responder Class
​ Gist: https://gist.github.com/KirkSteffke/0d1f22c77b6d3a2f5599
•  Test class for class making callout (part 1)
•  Utilizes Mock Responder Class (part 2)
•  Respond from callout comes fr...
HttpCalloutMock Interface
​ Part 2: Test Coverage
HttpCalloutMock Interface
​ Recap
•  Defined within test class
•  Returns single callout
response
•  WebServiceMock for WSDL
based SOAP callouts
•  HttpCallo...
StaticResourceCalloutMock
Part 1: The static resource
•  Only contains JSON text
•  Can’t be a zipped static resource w/ m...
StaticResourceCalloutMock
​ Part 2: Test coverage
•  Slightly different from last example
•  No separate class; defined with...
StaticResourceCalloutMock
​ Part 2: Test coverage
•  Defined within test class
•  Returns single callout
response
•  WebServiceMock for WSDL
based SOAP callouts
•  HttpCallo...
•  Two methods making callouts
•  One method using both
MultiStaticResourceCalloutMock
Part 1: Modified Scenario
•  Similar to StaticResourceCalloutMock
•  Define multiple endpoints
•  Each has own Static Resource
•  Still no Zip file
Mu...
MultiStaticResourceCalloutMock
Part 2: Test Coverage
1.  New MultiStaticResourceCalloutMock()
2.  Set endpoint based resou...
Complex Scenario
A hypothetical, but real-world model to help you digest
•  Batch job
•  Import of Contacts from remote system
•  Remote system has an API
•  Two endpoints
•  /api/ContactCount
• ...
•  /api/ContactCount
•  Returns # of Contact records to be imported
•  Result can change during span of batch execution
• ...
1.  Batch is called
2.  Start() method
3.  Each execute() iteration performs 2 callouts
4.  1st checks total # of records ...
Better Mock Structure
Using our tools to make better tools
Limitations
Working with Our Tools
•  HttpCalloutMock Interface and StaticResourceMock
•  Can only handle one endpoint
•  ...
Problems
Continued…
•  Challenge
•  Use one or more standard tool
•  Extend the usage
•  Create queue of expected response...
•  MakeCallouts Method
•  Loops 10x
•  Builds string of results
•  ContactCount Method
•  Makes Callout
•  Contacts Method...
•  ResponseMap
•  By method
•  By endpoint
•  List of responses
•  Respond
•  Verify request
•  Prepare response
•  Discar...
•  Body (JSON)
•  Status (success)
•  StatusCode
•  Discard
Building the Solution
TestCalloutResponseGenerator.Resp
// Cla...
•  ResponseMap
•  By method
•  By endpoint
•  List of responses
Building the Solution
TestCalloutResponseGenerator.getResp...
•  Respond
•  Verify request
•  Prepare response
•  Discard
Building the Solution
TestCalloutResponseGenerator.Respond(Htt...
•  Setup ResponseMap
•  Shortcut Explanation
•  Contacts
•  ContactCount
•  Discard
•  Set mock
•  Invoke Callout
Testing ...
Testing the Solution
Example4_TestCalloutClass.TestWithPattern()
// Temporary collection to hold looped results (for demo,...
•  Setup ResponseMap
•  Shortcut Explanation
•  Contacts
•  ContactCount
•  Discard
•  Set mock
•  Invoke Callout
Testing ...
Testing the Solution
TestCalloutClass
1.  Count or Contact callout
2.  # of loop iteration
3.  Count data 10, 12, 12, …
1....
Thank you
Upcoming SlideShare
Loading in …5
×

Enhanced Web Service Testing: A Better Mock Structure

383 views

Published on

Salesforce provides an interface for testing callouts named HttpCalloutMock used to cover remote callouts. While adequate for simple callouts, in the real world you often need something more flexible, as in the case of multiple and varying responses from the same or varying endpoints. More precise testing and coverage can be obtained by extending the standard interface. Join us as we demonstrate a solution to use to enable the flexibility required for complex integration and synchronization apps.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
383
On SlideShare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
7
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Enhanced Web Service Testing: A Better Mock Structure

  1. 1. Enhanced Web Service Testing ​ Kirk Steffke ​ AppExchange Practice Lead ​ kirk@crmscience.com ​ @kirkevonphilly ​  A Better Mock Structure
  2. 2. •  Test Coverage Overview •  Review of Native Testing Methods •  WebServiceMock and HttpCalloutMock Interfaces •  StaticResourceCalloutMock •  MultiStaticResourceCalloutMock •  Sample Scenario •  Better Mock Structure •  Setup •  Review Agenda ​ During this session
  3. 3. Test Coverage Overview The who, what, when, where, and why’s of creating coverage
  4. 4. •  Code we write… •  …about the code we just wrote •  Test classes •  Independent of code being tested •  One or more test methods •  @isTest annotation Apex Test Coverage ​ “What” with a Hint of “How”
  5. 5. Apex Test Coverage ​ A Bit More of “How” Grandma’s Famous Recipe 1.  Write code to do something 2.  Create test class 3.  Mark with @isTest 4.  Create test method 5.  Create test data 6.  Invoke code being tested 7.  Assert (+ or -)
  6. 6. •  Code we write… •  …about the code we just wrote •  Test classes •  Independent of code being tested •  One or more test method Apex Test Coverage Now More “What”
  7. 7. •  You, me, everyone •  Required •  75% •  Ongoing Development •  Help catch regressions •  Positive and negative assertions Apex Test Coverage ​ “Who-ot” and “Why”
  8. 8. •  As you’re developing •  Moving code from sandbox to production •  Creating packages Apex Test Coverage ​ When
  9. 9. Native Testing Strategies The Tools that are Already in your Toolbox
  10. 10. •  Class w/ single method that makes a callout Sample Scenario: Single Callout ​ Class Needing Coverage
  11. 11. Sample Scenario: Single Callout ​ Class Needing Coverage ​ Gist: https://gist.github.com/KirkSteffke/2ca4357c7e84eece9073
  12. 12. •  Method making callout needs coverage •  Don’t want test methods to actually make callout! •  Responses to be used for positive/negative tests Sample Scenario: Testing Objective ​ What We Need to Test
  13. 13. •  Defined within test class •  Returns single callout response •  WebServiceMock for WSDL based SOAP callouts •  HttpCalloutMock for testing Http callouts •  Defined by content of text file in Static Resource •  Returns response for a single endpoint •  Similar to StaticResourceCalloutMock •  Static Resource contains response for multiple endpoints WebServiceMock and HttpCalloutMock Interfaces StaticResourceCalloutMock MultiStaticResourceCalloutMo ck Documentation: bit.ly/df15mock3Documentation: bit.ly/df15mock1 Documentation: bit.ly/df15mock2 Options for Testing HTTP Callouts ​ Out-of-the-box Tools
  14. 14. •  Defined within test class •  Returns single callout response •  WebServiceMock for WSDL based SOAP callouts •  HttpCalloutMock for testing Http callouts •  Defined by content of text file in Static Resource •  Returns response for a single endpoint •  Similar to StaticResourceCalloutMock •  Static Resource contains response for multiple endpoints WebServiceMock and HttpCalloutMock Interfaces StaticResourceCalloutMock MultiStaticResourceCalloutMo ck Documentation: bit.ly/df15mock3Documentation: bit.ly/df15mock1 Documentation: bit.ly/df15mock2 Options for Testing HTTP Callouts ​ Out-of-the-box Tools
  15. 15. •  Not “the” test, but is @test •  Called by test class methods •  Returns “mock” responses •  Sets up header, body, and status code HttpCalloutMock Interface ​ Part 1: Mock Responder Class
  16. 16. HttpCalloutMock Interface ​ Part 1: Mock Responder Class ​ Gist: https://gist.github.com/KirkSteffke/0d1f22c77b6d3a2f5599
  17. 17. •  Test class for class making callout (part 1) •  Utilizes Mock Responder Class (part 2) •  Respond from callout comes from responder class (part 2) HttpCalloutMock Interface ​ Part 2: Test Coverage
  18. 18. HttpCalloutMock Interface ​ Part 2: Test Coverage
  19. 19. HttpCalloutMock Interface ​ Recap
  20. 20. •  Defined within test class •  Returns single callout response •  WebServiceMock for WSDL based SOAP callouts •  HttpCalloutMock for testing Http callouts •  Defined by content of text file in Static Resource •  Returns response for a single endpoint •  Similar to StaticResourceCalloutMock •  Static Resource contains response for multiple endpoints WebServiceMock and HttpCalloutMock Interfaces StaticResourceCalloutMock MultiStaticResourceCalloutMo ck Documentation: bit.ly/df15mock3Documentation: bit.ly/df15mock1 Documentation: bit.ly/df15mock2 Options for Testing HTTP Callouts ​ Out-of-the-box Tools
  21. 21. StaticResourceCalloutMock Part 1: The static resource •  Only contains JSON text •  Can’t be a zipped static resource w/ multiple files •  mock.setStaticResource(‘Name of Zip’,’File in Zip’)
  22. 22. StaticResourceCalloutMock ​ Part 2: Test coverage •  Slightly different from last example •  No separate class; defined within coverage •  StaticResourceCalloutMock object instead of class with HttpCalloutMock interface
  23. 23. StaticResourceCalloutMock ​ Part 2: Test coverage
  24. 24. •  Defined within test class •  Returns single callout response •  WebServiceMock for WSDL based SOAP callouts •  HttpCalloutMock for testing Http callouts •  Defined by content of text file in Static Resource •  Returns response for a single endpoint •  Similar to StaticResourceCalloutMock •  Static Resource contains response for multiple endpoints WebServiceMock and HttpCalloutMock Interfaces StaticResourceCalloutMock MultiStaticResourceCalloutMo ck Documentation: bit.ly/df15mock3Documentation: bit.ly/df15mock1 Documentation: bit.ly/df15mock2 Options for Testing HTTP Callouts ​ Out-of-the-box Tools
  25. 25. •  Two methods making callouts •  One method using both MultiStaticResourceCalloutMock Part 1: Modified Scenario
  26. 26. •  Similar to StaticResourceCalloutMock •  Define multiple endpoints •  Each has own Static Resource •  Still no Zip file MultiStaticResourceCalloutMock Part 2: Test Coverage Static Resource: Example3_bar1 Static Resource: Example3_bar2
  27. 27. MultiStaticResourceCalloutMock Part 2: Test Coverage 1.  New MultiStaticResourceCalloutMock() 2.  Set endpoint based resources 3.  Set mock 4.  Invoke callouts 5.  Perform assertions
  28. 28. Complex Scenario A hypothetical, but real-world model to help you digest
  29. 29. •  Batch job •  Import of Contacts from remote system •  Remote system has an API •  Two endpoints •  /api/ContactCount •  /api/Contacts Remote Import via Batch Overview
  30. 30. •  /api/ContactCount •  Returns # of Contact records to be imported •  Result can change during span of batch execution •  /api/Contacts •  Returns x number of contacts •  Paginated •  Page=1 – returns 1st 200 records •  Page=2 – returns 2nd 200 records •  …and so on Remote Import via Batch Meet the Endpoints
  31. 31. 1.  Batch is called 2.  Start() method 3.  Each execute() iteration performs 2 callouts 4.  1st checks total # of records available 5.  2nd imports records 6.  End of execute determines if more records? 7.  Execute may run for many iterations 8.  If no more records or limits, finish() 9.  Restart cycle 10.  End Remote Import via Batch Batch Flow
  32. 32. Better Mock Structure Using our tools to make better tools
  33. 33. Limitations Working with Our Tools •  HttpCalloutMock Interface and StaticResourceMock •  Can only handle one endpoint •  Single response •  Single setup prior to invoking actual callouts •  MultiStaticResourceCalloutMock •  Handle multiple endpoints •  Single response for each •  Single setup prior to invoking actual callouts
  34. 34. Problems Continued… •  Challenge •  Use one or more standard tool •  Extend the usage •  Create queue of expected responses per endpoint •  Consider re-usability
  35. 35. •  MakeCallouts Method •  Loops 10x •  Builds string of results •  ContactCount Method •  Makes Callout •  Contacts Method •  Makes callout •  Page Parameter Building the Problem Example4_CalloutClass.apxc public class Example4_CalloutClass { // Method to simulate all the callouts in our batch flow public static string MakeCallouts() { // Property to return string results = ''; // Let's call each resource 10x and... for (integer i = 0; i < 10; i++) { // ...add to results string this Count's response's body results += 'Count (' + i + '): ' + CalloutContactCount().getBody() + 'rn'; // ...add to results string this Contact response's body results += 'Contacts (' + i + '): ' + CalloutContacts(i).getBody() + 'rn'; } // Return concatenated string of results return results;
  36. 36. •  ResponseMap •  By method •  By endpoint •  List of responses •  Respond •  Verify request •  Prepare response •  Discard •  Resp Class Building the Solution TestCalloutResponseGenerator.apxc @isTest global class TestCalloutResponseGenerator implements HttpCalloutMock { // Property and getter (semi init'd) to pair method --> endpoint --> list of responses private static map<string, map<string, list<resp>>> ResponseMap; public static map<string, map<string, list<resp>>> getResponseMap() { if (ResponseMap == null) { ResponseMap = new map<string, map<string, list<resp>>>(); // For each setMethod() method type, pre-pop. w/ empty map for (string method :new list<string>{'GET','PUT','POST','DELETE','HEAD','TRACE'}) ResponseMap.put(method, new map<string, list<resp>>()); } return ResponseMap; } // Required respond() method for HttpCalloutMock public HttpResponse Respond(HttpRequest req) {
  37. 37. •  Body (JSON) •  Status (success) •  StatusCode •  Discard Building the Solution TestCalloutResponseGenerator.Resp // Class to hold details of response from within test methods public class Resp { public string body { get; set; } public string status { get; set; } public integer statusCode { get; set; } public boolean discard { get; set; } public Resp(string body, string status, integer statusCode, boolean discard) { this.body = body; this.status = status; this.statusCode = statusCode; this.discard = discard; } }
  38. 38. •  ResponseMap •  By method •  By endpoint •  List of responses Building the Solution TestCalloutResponseGenerator.getResponseMap() // Property to pair method --> endpoint --> list of responses private static map<string, map<string, list<resp>>> ResponseMap; // Getter to return or prepare a semi init'd response map public static map<string, map<string, list<resp>>> getResponseMap() { if (ResponseMap == null) { ResponseMap = new map<string, map<string, list<resp>>>(); // For each setMethod() method type, pre-pop. w/ empty map for (string method :new list<string>{'GET','PUT','POST','DELETE','HEAD','TRACE'}) ResponseMap.put(method, new map<string, list<resp>>()); } return ResponseMap; }
  39. 39. •  Respond •  Verify request •  Prepare response •  Discard Building the Solution TestCalloutResponseGenerator.Respond(HttpRequest req) // Required respond() method for HttpCalloutMock public HttpResponse Respond(HttpRequest req) { // Property for returned response HttpResponse res = new HttpResponse(); // Ensure HttpRequest is valid if (req != null && !string.isBlank(req.getMethod()) && !string.isBlank(req.getEndPoint())) { // Verify the Response map contains the req's method and endpoint if (getResponseMap().containsKey(req.getMethod()) && getResponseMap().get(req.getMethod()).containsKey(req.getEndpoint()) ) { // Instantiate a list of the method/endpoint's response bodies list<resp> respList = getResponseMap().get(req.getMethod()).get(req.getEndpoint()); // If there's at least one, use it - otherwise, output an error if (!respList.isEmpty()) {
  40. 40. •  Setup ResponseMap •  Shortcut Explanation •  Contacts •  ContactCount •  Discard •  Set mock •  Invoke Callout Testing the Solution Example4_TestCalloutClass.TestWithPattern() @isTest public class Example4_TestCalloutClass { public static testMethod void TestWithPattern() { /* The below is just a shortcut for the demo. Instead of this 10x (1 per page): TestCalloutResponseGenerator.getResponseMap().get('GET').put( '/api/Contacts?page=1', new list<TestCalloutResponseGenerator.Resp> { new TestCalloutResponseGenerator.Resp( '{"Contacts":"data...page 1'"}', 'success', 200, false ) } ); */ // Temporary collection to hold looped results (for demo, we don't care about actual data,
  41. 41. Testing the Solution Example4_TestCalloutClass.TestWithPattern() // Temporary collection to hold looped results (for demo, we don't care about actual data, just proof of concept) map<string, list<TestCalloutResponseGenerator.Resp>> pagedResponses = new map<string, list<TestCalloutResponseGenerator.Resp>>(); // Create a dummy response for Contact callout for our "10" pages of contacts for (integer i = 0; i < 10; i++) { // Each loop = 1 page pagedResponses.put('/api/Contacts?page=' + i, new list<TestCalloutResponseGenerator.Resp>{ new TestCalloutResponseGenerator.Resp( '{"Contacts":"data...' + i + '"}', 'success', 200, true) }); } // Add all of the contact responses to the response map TestCalloutResponseGenerator.getResponseMap().put('GET', pagedResponses);
  42. 42. •  Setup ResponseMap •  Shortcut Explanation •  Contacts •  ContactCount •  Discard •  Set mock •  Invoke Callout Testing the Solution Example4_TestCalloutClass.TestWithPattern() // Enable mock response Test.setMock(HttpCalloutMock.class, new TestCalloutResponseGenerator()); // Invoke the callout method string results = Example4_CalloutClass.MakeCallouts(); system.debug('Results: rnrn' + results);
  43. 43. Testing the Solution TestCalloutClass 1.  Count or Contact callout 2.  # of loop iteration 3.  Count data 10, 12, 12, … 1.  Use 1st, discard 2.  Use 2nd, don’t discard 4.  Contact data increments 1.  Provided per page response
  44. 44. Thank you

×