Apex Integration Services

Apex Integration Overview

Describe the differences between web service and HTTP callouts. Authorize an external site with remote site settings.
  • Make Callouts to External Services from Apex
    • Apex Callouts enable you to tightly integrate Apex code with an external service. Two basic types:
      • SOAP: Web service callouts to SOAP use XML, and usually require a WSDL (“wiz-dull”) document for code generation
      • REST: HTTP callouts typically use REST with JSON
      • These types are similar in terms of sending a request and receiving a response, but:
        • WSDL-based callouts apply to SOAP
        • HTTP callouts work with any HTTP service (SOAP or REST)
    • Whenever possible, use an HTTP service
      • Easier to interact with, require less code, and utilize easily readable JSON
      • REST is newer and more commonly used
      • SOAP is mostly used when integrating with legacy applications or transactions that require a formal exchange format or stateful interactions
  • Authorize Endpoint Addresses
    • Any time you make a callout to an external site Salesforce makes sure it is authorized
      • So, before you start working with callouts, update the list of approved sites for your org on the Remote Site Settings page
    • Setup > Quick Find > “Remote Site” > Remote Site Settings
      • Adding a remote site URL such as https://th-apex-http-callout.herokuapp.com authorizes all subfolders for the endpoint, like https://th-apex-http-callout.herokuapp.com/path1

Apex REST Callouts

Perform a callout to receive data from an external service. Perform a callout to send data to an external service. Test callouts by using mock callouts.
  • HTTP And Callout Basics
    • REST callouts are based on HTTP
    • Each callout request is associated with an HTTP method and an endpoint
    • Simplest request is a GET request (GET is an HTTP method). Other methods follow:
      • GET: Retrieve data identified by a URL
      • POST: Create a resource or post data to the server
      • DELETE: Delete a resource identified by a URL
      • PUT: Create or replace the resource sent in the request body
    • Each request also sets a URI, which is the endpoint address where the service is located
    • When the server processes the request, it sends a status code in the response. Example codes:
      • 200: request successful
      • 404: file not found
      • 500: internal service error
  • Get Data from a Service
    • Note that accessing the URL above returns JSON formatted like the following
      • {"animals":["majestic badger","fluffy bunny","scary bear","chicken"]}
    • Service sends the response in JSON format, which is essentially a string. The following code uses the built-in JSONParser class to convert it to an object.
      • Run it using an Execute Anonymous window to see the result below
    • JSON for this example is simple and pretty easy to parse
      • For more complex JSON structures, you can use JSON2Apex
      • This tool generates strongly typed Apex code for parsing JSON structures - just paste in the JSON and it generates the Apex for parsing it
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
request.setMethod('GET');
HttpResponse response = http.send(request);
// If the request is successful, parse the JSON response.
if(response.getStatusCode() == 200) {
    // Deserialize the JSON string into collections of primitive data types.
    Map<String, Object> results=(Map<String, Object>)JSON.deserializeUntyped(response.getBody());
    // Cast the values in the 'animals' key as a list
    List<Object> animals = (List<Object>) results.get('animals');
    System.debug('Received the following animals:');
    for(Object animal: animals) {
        System.debug(animal);
    }
}

  • Send Data to a Service
    • POSTing data to a service is sending data to the service
    • Example below sends a POST request to the web service to add an animal name
      • New name is sent as a JSON string
      • Content-Type header is set to let the service know the sent data is in JSON format so it can process the data appropriately
    • If request was processed successfully, returns status code 201, indicating a resource has been created
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
request.setMethod('POST');
request.setHeader('Content-Type', 'application/json;charset=UTF-8');
// Set the body as a JSON object
request.setBody('{"name":"mighty moose"}');
HttpResponse response = http.send(request);
// Parse the JSON response
if(response.getStatusCode() != 201) {
    System.debug('The status code returned was not expected: ' 
                 + response.getStatusCode() 
                 + ' ' 
                 + response.getStatus());
} else {
    System.debug(response.getBody());
}

  • Test Callouts
    • Apex test methods do not support callouts - tests that perform callouts fail
    • But, the testing runtime allows you to “mock” the callout, which allows you to specify the response to return in the test instead of actually calling the web service
    • Essentially, you are telling the runtime “The web service will return X. Instead of calling the service, just return X.”
    • Using mock callouts helps ensure you attain adequate code coverage and that no lines of code are skipped due to callouts
  • Prerequisites
    • Following class contains modified versions of the GET and POST request examples executed anonymously in the previous section
    • Dev Console > File > New > Apex Class, then use class name: “AnimalsCallout”

AnimalsCallouts.apxc

public class AnimalsCallouts {
    public static HttpResponse makeGetCallout() {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
        request.setMethod('GET');
        HttpResponse response = http.send(request);
        // If the request is successful, parse the JSON response.
        if(response.getStatusCode() == 200) {
            // Deserializes the JSON string into collections of primitive data types.
            Map<String, Object> results = (
                (Map<String, Object>) JSON.deserializeUntyped(response.getBody()));
            // Cast the values in the 'animals' key as a list
            List<Object> animals = (List<Object>) results.get('animals');
            System.debug('Received the following animals:');
            for(Object animal: animals) {
                System.debug(animal);
            }
        }
        return response;
    }
    public static HttpResponse makePostCallout() {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
        request.setMethod('POST');
        request.setHeader('Content-Type', 'application/json;charset=UTF-8');
        request.setBody('{"name":"mighty moose"}');
        HttpResponse response = http.send(request);
        // Parse the JSON response
        if(response.getStatusCode() != 201) {
            System.debug('The status code returned was not expected: ' +
                response.getStatusCode() + ' ' + response.getStatus());
        } else {
            System.debug(response.getBody());
        }
        return response;
    }        
}
  • Test a Callout with StaticResourceCalloutMock
    • To test callouts, use mock callouts by either implementing an interface or using static resources. This example uses static resources and a mock interface later on.
      • Apex runtime knows to look up the response specified in the static resource and returns it
    • Test.setMock method informs the runtime that mock callouts are used in the test method
    • Set up via: Dev Console > File > New > Static Resource
      • Call it GetAnimalResource
      • Select MIME type text/plain for the JSON
      • Add the following content to the text file:
        • {"animals": ["pesky porcupine", "hungry hippo", "squeaky squirrel"]}
    • Now, create a test for callout that uses that resource
      • Set up via: Dev Console > File > New > Apex Class
      • Check the checkbox “Always Run Asynchronously” under the Test menu
      • Run the test via: Test > New Run
@isTest
private class AnimalsCalloutsTest {
    @isTest static  void testGetCallout() {
        // Create the mock response based on a static resource
        StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
        mock.setStaticResource('GetAnimalResource');
        mock.setStatusCode(200);
        mock.setHeader('Content-Type', 'application/json;charset=UTF-8');
        // Associate the callout with a mock response
        Test.setMock(HttpCalloutMock.class, mock);
        // Call method to test
        HttpResponse result = AnimalsCallouts.makeGetCallout();
        // Verify mock response is not null
        System.assertNotEquals(null,result, 
                               'The callout returned a null response.');
        // Verify status code
        System.assertEquals(200,result.getStatusCode(), 'The status code is not 200.');
        // Verify content type   
        System.assertEquals('application/json;charset=UTF-8',
          result.getHeader('Content-Type'),
          'The content type value is not expected.');  
        // Verify the array contains 3 items     
        Map<String, Object> results = (Map<String, Object>) 
            JSON.deserializeUntyped(result.getBody());
        List<Object> animals = (List<Object>) results.get('animals');
        System.assertEquals(3, animals.size(), 
                            'The array should only contain 3 items.');          
    }   
}

  • Test a Callout with HttpCalloutMock
    • To test your POST callout, this section provides an implementation of the HttpCalloutMock interface
      • Interface enables you to specify the response that’s sent in the respond method
      • Test class instructs Apex runtime to send this fake response by calling Test.setMock again
      • First argument, pass HttpCalloutMock.class
      • Second argument, pass a new instance of AnimalsHttpCalloutMock, an implementation of HttpCalloutMock
    1. First, create new Apex class AnimalsHttpCalloutMock, as shown below.
    2. Then modify the test class AnimalsCalloutsTest to add the second test method, testPostCallout, shown below.
    3. Run the tests

AnimalsHttpCalloutMock.apxc

@isTest
global class AnimalsHttpCalloutMock implements HttpCalloutMock {
    // Implement this interface method
    global HTTPResponse respond(HTTPRequest request) {
        // Create a fake response
        HttpResponse response = new HttpResponse();
        response.setHeader('Content-Type', 'application/json');
        response.setBody('{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}');
        response.setStatusCode(200);
        return response; 
    }
}

Updated AnimalsCalloutsTest.apxc

@isTest
private class AnimalsCalloutsTest {
    @isTest static  void testGetCallout() {
        // Create the mock response based on a static resource
        StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
        mock.setStaticResource('GetAnimalResource');
        mock.setStatusCode(200);
        mock.setHeader('Content-Type', 'application/json;charset=UTF-8');
        // Associate the callout with a mock response
        Test.setMock(HttpCalloutMock.class, mock);
        // Call method to test
        HttpResponse result = AnimalsCallouts.makeGetCallout();
        // Verify mock response is not null
        System.assertNotEquals(null,result, 
                               'The callout returned a null response.');
        // Verify status code
        System.assertEquals(200,result.getStatusCode(), 'The status code is not 200.');
        // Verify content type   
        System.assertEquals('application/json;charset=UTF-8',
          result.getHeader('Content-Type'),
          'The content type value is not expected.');  
        // Verify the array contains 3 items     
        Map<String, Object> results = (Map<String, Object>) 
            JSON.deserializeUntyped(result.getBody());
        List<Object> animals = (List<Object>) results.get('animals');
        System.assertEquals(3, animals.size(), 
                            'The array should only contain 3 items.');          
    }
    @isTest static void testPostCallout() {
        // Set mock callout class 
        Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock()); 
        // This causes a fake response to be sent
        // from the class that implements HttpCalloutMock. 
        HttpResponse response = AnimalsCallouts.makePostCallout();
        // Verify that the response received contains fake values
        String contentType = response.getHeader('Content-Type');
        System.assert(contentType == 'application/json');
        String actualValue = response.getBody();
        System.debug(response.getBody());
        String expectedValue = '{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}';
        System.assertEquals(expectedValue, actualValue);
        System.assertEquals(200, response.getStatusCode());
    }
}


  • The class below calls the URL https://th-apex-http-callout.herokuapp.com/animals/1, where 1 is an ID parameter that can be changed
  • Then, it parses the resulting JSON response, for example: {"animal":{"id":1,"name":"chicken","eats":"chicken food","says":"cluck cluck"}}

AnimalLocator.apxc

public class AnimalLocator {
    public static String getAnimalNameById(Integer intId) {
        Http http = new Http();
        HttpRequest request = new HttpRequest();       
        request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals/' + intId);
        request.setMethod('GET');
        HttpResponse response = http.send(request);
        if(response.getStatusCode() != 200) {
            System.debug('The status code returned was not expected: ' +
                response.getStatusCode() + ' ' + response.getStatus());
        } else {
            Map<String, Object> results = ((Map<String, Object>)
                                           JSON.deserializeUntyped(response.getBody()));
            Map<String, Object> animal = (Map<String, Object>) results.get('animal');
            String name = (String) animal.get('name');
            return(name);
        }
        return('error');
    }
}

The following code can be used to call AnimalLocator in an Execute Anonymous window

AnimalLocator al = new AnimalLocator();
System.debug(al.getAnimalNameById(1));

It returns the following:

The following test code results in 100% test coverage of the AnimalLocator class

AnimalLocatorMock.apxc

@isTest
global class AnimalLocatorMock implements HttpCalloutMock {
    // Implement this interface method
    global HTTPResponse respond(HTTPRequest request) {
        // Create a fake response
        HttpResponse response = new HttpResponse();
        response.setHeader('Content-Type', 'application/json');
        response.setBody('{"animal":{"id":1,"name":"chicken","eats":"chicken food","says":"cluck cluck"}}');
        response.setStatusCode(200);
        return response; 
    }
}

AnimalLocatorTest.apxc

@isTest
private class AnimalLocatorTest {
    @isTest static void testGetCallout() {
        // Set mock callout class
        Test.setMock(HttpCalloutMock.class, new AnimalLocatorMock());
        // This causes a fake response to be sent
        // from the class that implements HttpCalloutMock
        String returnValue = AnimalLocator.getAnimalNameById(1);
        System.assert(returnValue == 'Chicken');
    }
}

Apex SOAP Callouts

Generate Apex classes using WSDL2Apex. Perform a callout to send data to an external service using SOAP. Test callouts by using mock callouts.
  • Use WSDL2Apex to Generate Apex Code
    • Apex can also make callouts to SOAP web services using XML, but working with SOAP can be painful
    • Download the web service’s WSDL file, then upload the WSDL, and WSDL2Apex generates the Apex classes for you
      • Apex classes construct the SOAP XML, transmit the data, and parse the response XML into Apex objects
        • Works similar to WSDL2Java or importing a WSDL as a Web Reference in .NET
        • Note: use outbound messaging to handle integration solutions when possible - use third-party web services only when necessary
      • The Apex classes generated by WSDL2Apex internally handle the logic to construct and parse the XML of the web service messages
    • This example uses a calculator.xml WSDL file
  • Generate an Apex Class from the WSDL
    • To use the WSDL2Apex tool, go to Setup > Quick Find > “Apex Classes” > Apex Classes, then click the “Generate from WSDL” button, upload the XML, then click “Generate Apex”
    • The final page of the wizard shows the generated classes
    • The calculator.xml file generated two Apex classes: AsyncCalculatorServices and calculatorServices
  • Execute the Callout
    • Now, execute the callout and see if it correctly adds two numbers using the following code in Execute Anonymous
calculatorServices.CalculatorImplPort calculator = new  calculatorServices.CalculatorImplPort();
Double x = 1.0;
Double y = 2.0;
Double result = calculator.doAdd(x,y);
System.debug(result);

  • Test Web Service Callouts
    • The 75% test code coverage requirement includes classes generated by WSDL2Apex, but test methods don’t support web service callouts
    • To prevent tests from cailing and to increase code coverage, Apex provides a built-in WebServiceMock interface and the Test.setMock method
    • Use this interface to receive fake responses in a test method, thereby providing the necessary test coverage
  • Specify a Mock Response for Callouts
    • When you create an Apex class from a WSDL, the methods in the class call WebServiceCallout.invoke, which performs the callout to the services
    • When testing these methods, you can instruct the Apex runtime to generate a fake response whenever WebServiceCallout.invoke is called
      • To do so, implement the WebServiceMock interface and specify a fake response for the testing runtime to send
    • Instruct the Apex runtime to send this fake response by calling Test.setMock in your test method
      • First argument: WebServiceMock.class
      • Second argument: new instance of your WebServiceMock interface implementation
Test.setMock(WebServiceMock.class, new MyWebServiceMockImpl());
  • Full worked example:
  1. Dev Console > File > New > Apex Class > AwesomeCalculator

AwesomeCalculator.apxc

public class AwesomeCalculator {
    public static Double add(Double x, Double y) {
        calculatorServices.CalculatorImplPort calculator = 
            new calculatorServices.CalculatorImplPort();
        return calculator.doAdd(x,y);
    }
}
  1. Dev Console > File > New > Apex Class > CalculatorCalloutMock

CalculatorCalloutMock.apxc

@isTest
global class CalculatorCalloutMock implements WebServiceMock {
   global void doInvoke(
           Object stub,
           Object request,
           Map<String, Object> response,
           String endpoint,
           String soapAction,
           String requestName,
           String responseNS,
           String responseName,
           String responseType) {
        // start - specify the response you want to send
        calculatorServices.doAddResponse response_x = 
            new calculatorServices.doAddResponse();
        response_x.return_x = 3.0;
        // end
        response.put('response_x', response_x); 
   }
}
  1. Dev Console > File > New > Apex Class > AwesomeCalculatorTest

AwesomeCalculatorTest.apxc

@isTest
private class AwesomeCalculatorTest {
    @isTest static void testCallout() {              
        // This causes a fake response to be generated
        Test.setMock(WebServiceMock.class, new CalculatorCalloutMock());
        // Call the method that invokes a callout
        Double x = 1.0;
        Double y = 2.0;
        Double result = AwesomeCalculator.add(x, y);
        // Verify that a fake result is returned
        System.assertEquals(3.0, result); 
    }
}
  1. Run all tests with Dev Console > Test > Run All
    • Result is 100% code coverage on the AwesomeCalculator class

Worked example:

  1. Generate an Apex class using this WSDL file

ParkService.apxc

//Generated by wsdl2apex

public class ParkService {
    public class byCountryResponse {
        public String[] return_x;
        private String[] return_x_type_info = new String[]{'return','http://parks.services/',null,'0','-1','false'};
        private String[] apex_schema_type_info = new String[]{'http://parks.services/','false','false'};
        private String[] field_order_type_info = new String[]{'return_x'};
    }
    public class byCountry {
        public String arg0;
        private String[] arg0_type_info = new String[]{'arg0','http://parks.services/',null,'0','1','false'};
        private String[] apex_schema_type_info = new String[]{'http://parks.services/','false','false'};
        private String[] field_order_type_info = new String[]{'arg0'};
    }
    public class ParksImplPort {
        public String endpoint_x = 'https://th-apex-soap-service.herokuapp.com/service/parks';
        public Map<String,String> inputHttpHeaders_x;
        public Map<String,String> outputHttpHeaders_x;
        public String clientCertName_x;
        public String clientCert_x;
        public String clientCertPasswd_x;
        public Integer timeout_x;
        private String[] ns_map_type_info = new String[]{'http://parks.services/', 'ParkService'};
        public String[] byCountry(String arg0) {
            ParkService.byCountry request_x = new ParkService.byCountry();
            request_x.arg0 = arg0;
            ParkService.byCountryResponse response_x;
            Map<String, ParkService.byCountryResponse> response_map_x = new Map<String, ParkService.byCountryResponse>();
            response_map_x.put('response_x', response_x);
            WebServiceCallout.invoke(
              this,
              request_x,
              response_map_x,
              new String[]{endpoint_x,
              '',
              'http://parks.services/',
              'byCountry',
              'http://parks.services/',
              'byCountryResponse',
              'ParkService.byCountryResponse'}
            );
            response_x = response_map_x.get('response_x');
            return response_x.return_x;
        }
    }
}
  1. The following ParkLocator class contains a method called country that uses the ParkService class.
public class ParkLocator {
    public static String[] country(String country){
        ParkService.ParksImplPort parks = new ParkService.ParksImplPort();
        String[] parksname = parks.byCountry(country);
        return parksname;
    }
}
  1. The following mock class ParkServiceMock mocks the callout response:
@isTest
global class ParkServiceMock implements WebServiceMock {
   global void doInvoke(
           Object stub,
           Object request,
           Map<String, Object> response,
           String endpoint,
           String soapAction,
           String requestName,
           String responseNS,
           String responseName,
           String responseType) {
        // start - specify the response you want to send
        ParkService.byCountryResponse response_x = 
            new ParkService.byCountryResponse();
            
        List<String> myStrings = new List<String> {'Park1','Park2','Park3'};
    
        response_x.return_x = myStrings;
        // end
        response.put('response_x', response_x); 
   }
}
  1. The following test class ParkLocatorTest contains unit tests for the ParkService class
@isTest
private class ParkLocatorTest  {
    @isTest static void testCallout() {              
        // This causes a fake response to be generated
        Test.setMock(WebServiceMock.class, new ParkServiceMock());
        // Call the method that invokes a callout
        List<String> result = new List<String>();
        List<String> expectedvalue = new List<String>{'Park1','Park2','Park3'};
        
        result = ParkLocator.country('India');
        // Verify that a fake result is returned
        System.assertEquals(expectedvalue, result); 
    }
}

Apex Web Services

Describe the two types of Apex web services and provide a high-level overview of these services. Create an Apex REST class that contains methods for each HTTP method. Invoke a custom Apex REST method with an endpoint. Pass data to a custom Apex REST method by sending a request body in JSON format. Write a test method for an Apex REST method and set properties in a test REST request. Write a test method for an Apex REST method by calling the method with parameter values.
  • Expose Your Apex Class as a Web Service
    • You can expose your Apex class methods as a REST or SOAP web service operation - by making methods callable through the web, your external applications can integrate with Salesforce
    • For example, imagine your company’s call center uses an internal application to manage resources
      • Custom support representatives are expected to use the same application to perform their daily work, including managing case records in Salesforce
      • By using one interface, reps can view and update case records and access internal resources
  • Expose a Class as a REST Service
    • Making Apex classes available as a REST web service by:
      • Defining your class as global
      • Defining your methods as global static
      • Add the following annotations to the class and methods. Ex: sample Apex REST class below uses one method
        • getRecord method is a custom REST API call
        • Annotated with @HttpGet and is invoked for a GET request
        • Class is annotated with @RestResource(urlMapping='/Account/*')
          • Base endpoint for Apex REST is https://yourInstance.my.salesforce.com/services/apexrest
            For this org, it could look like https://yourInstance.my.salesforce.com/services/apexrest/Account/*
        • URL mapping is case-sensitive and can contain a wildcard character (*)
Annotation Action Details
@HttpGet Read Reads or retrieves records
@HttpPost Create Creates records
@HttpDelete Delete Deletes records
@HttpPut Upsert Typically used to update existing records or create records
@HttpPatch Update Typically used to update fields in existing records
@RestResource(urlMapping='/Account/*')
global with sharing class MyRestResource {
    @HttpGet
    global static Account getRecord() {
        // Add your code
    }
}
  • Expose a Class as a SOAP Service
    • Make your Apex class available as a SOAP web service by:
      • Defining your class as gobal
      • Add keyword webservice and the static definition modifier to each method
        • webservice keyword provides global access to the method it is related to
      • Example below is a sample class with one method
      • External application can call your custom Apex methods as web service operations by consuming the class WSDL file
        • Generate this WSDL file for you class from the class detail page, access from the Apex Classes page in Setup. Typically, send the WSDL file to third-party developers to write integrations for your web service
        • The web service will require authentication, using either the Enterprise WSDL or Partner WSDL for login functionality
global with sharing class MySOAPWebService {
    webservice static Account getRecord(String id) {
        // Add your code
    }
}
  • Apex REST Walkthrough
    • Next few steps walk you through the process of building an Apex REST service. Steps:
      1. Create Apex class that’s exposed as a REST service
      2. Try calling a few methods from a client
      3. Write unit tests
    • The Apex class manages case records. Class contains five methods, and each method corresponds to an HTTP method
      • Ex: when the client application invokes a REST call for the GET HTTP method, the getCaseById method is invoked
      • The class is defined with a URL mapping of /Cases/*, so the endpoint used to call this REST service is any URI that starts with:
        https://yourInstance.my.salesforce.com/services/apexrest/Cases/
    • Best practice is to consider versioning your API endpoints so that you can provide upgrades in functionality without breaking existing code:
      • Ex: could create two classes specifying URL mappings of /Cases/v1/* and /Cases/v2/* to implement this
    • First step: create the following Apex REST class via: Dev Console > File > New > Apex Class

CaseManager.apxc

@RestResource(urlMapping='/Cases/*')
global with sharing class CaseManager {
    @HttpGet
    global static Case getCaseById() {
        RestRequest request = RestContext.request;
        // grab the caseId from the end of the URL
        String caseId = request.requestURI.substring(
          request.requestURI.lastIndexOf('/')+1);
        Case result =  [SELECT CaseNumber,Subject,Status,Origin,Priority
                        FROM Case
                        WHERE Id = :caseId];
        return result;
    }
    @HttpPost
    global static ID createCase(String subject, String status,
        String origin, String priority) {
        Case thisCase = new Case(
            Subject=subject,
            Status=status,
            Origin=origin,
            Priority=priority);
        insert thisCase;
        return thisCase.Id;
    }   
    @HttpDelete
    global static void deleteCase() {
        RestRequest request = RestContext.request;
        String caseId = request.requestURI.substring(
            request.requestURI.lastIndexOf('/')+1);
        Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId];
        delete thisCase;
    }     
    @HttpPut
    global static ID upsertCase(String subject, String status,
        String origin, String priority, String id) {
        Case thisCase = new Case(
                Id=id,
                Subject=subject,
                Status=status,
                Origin=origin,
                Priority=priority);
        // Match case by Id, if present.
        // Otherwise, create new case.
        upsert thisCase;
        // Return the case ID.
        return thisCase.Id;
    }
    @HttpPatch
    global static ID updateCaseFields() {
        RestRequest request = RestContext.request;
        String caseId = request.requestURI.substring(
            request.requestURI.lastIndexOf('/')+1);
        Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId];
        // Deserialize the JSON string into name-value pairs
        Map<String, Object> params = (Map<String, Object>)JSON.deserializeUntyped(request.requestbody.tostring());
        // Iterate through each parameter field and value
        for(String fieldName : params.keySet()) {
            // Set the field and value on the Case sObject
            thisCase.put(fieldName, params.get(fieldName));
        }
        update thisCase;
        return thisCase.Id;
    }    
}
  • Create a Record with a Post method
    • To invoke your REST service, you need a REST client. Example REST clients:
      • Your own API client
      • cURL command-line tool
      • curl library for PHP
      • Workbench tool
    • Apex REST supports two formats for representations of resources: JSON and XML
      • JSON is passed by default in the body of a request or response, format is indicated by the Content-Type property in the HTTP header
      • JSON is easier to read and understand than XML so it is used here to send a case record in JSON format
    • Apex REST supports OAUTH 2.0 and session authentication mechanisms, which means Salesforce uses industry standards to keep your application and data safe
      • You can use Workbench uses session authentication and can make testing easier
        • Workbench is a powerful, web-based suite of tools for admins/developers to interact with orgs via Lightning Platform APIs
        • To use Workbench to create records for this example
          1. Log into Salesforce. Then, go to https://workbench.developerforce.com/login.php
          2. Select Production
          3. Select your API version
          4. Accept the terms of service, the Login with Salesforce
          5. Click Allow to allow Workbench to access your information
          6. Enter your login credentials then Log in to Salesforce
          7. After logging in, select Utilities > REST Explorer
          8. Select POST
          9. URL path that the REST Explorer accepts is relative to the instance URL of the org. So, provide only the path that is appended to the instance URL. In this example, replace the default URI with /services/apexrest/Cases/
          10. Add the JSON string representation of the object to insert in the code block below.
          11. Click Execute. This calls the method associated with the POST HTTP method, specifically the createCase method.
          12. To view the response returned, click Show Raw Response, which includes the return value, in my case 500Dn000004iWchIAE
{
  "subject" : "Bigfoot Sighting!",
  "status" : "New",
  "origin" : "Phone",
  "priority" : "Low"
}

  • Retrieve Data with a Custom GET Method
    • By following similar steps as before, use Workbench to invoke the GET HTTP method:
      1. In Workbench, select GET
      2. Enter the URI /services/apexrest/Cases/<Record ID>, replacing <Record ID> with the ID in the record from the previous step
      3. Click Execute. This calls the method associated with the GET HTTP method, namely getCaseById

  • Retrive Data using cURL
    • Every good developer should know how to use cURL
      • cURL is a command-line tool for getting or sending files using URL syntax - it is useful for working with REST endpoints
    • Instead of using Workbench for your Apex REST service, you can use cURL to invoke the GET HTTP method
      • Note each time you “cURL” your REST endpoint, you pass the session ID for authorization. Workbench passes the session ID for you after you log in
      • You also need a Security Token
      • See the Trailhead for more details
    1. Use your Terminal (macOS/Linux) or Command Prompt (Windows) to execute the first code snippet below. If successful, it will return a result with an access_token, which is your session ID and instance_url for your org. It will look similar to the image below.
      curl -v https://login.salesforce.com/services/oauth2/token -d "grant_type=password" -d "client_id=<your_consumer_key>" -d "client_secret=<your_consumer_secret>" -d "username=<your_username>" -d "password=<your_password_and_security_token>" -H "X-PrettyPrint:1"
    2. Next, enter your cURL command, which will be similar to the following to call your Apex REST service and return case info.
      curl https://yourInstance.my.salesforce.com/services/apexrest/Cases/<Record_ID> -H "Authorization: Bearer <your_session_id>" -H "X-PrettyPrint:1"curl https://yourInstance.my.salesforce.com/services/apexrest/Cases/<Record_ID> -H "Authorization: Bearer <your_session_id>" -H "X-PrettyPrint:1"

  • Update Data with a Custom PUT or PATCH Method
    • Update records with the PUT or PATCH HTTP methods
    • PUT method is essentially an upsert: It either updates the entire resource, if it exists, or creates the resource if it doesn’t exist.
    • PATCH method updates only the specified portions of an existing resource
      • In Apex, update operations update only the specified fields and don’t overwrite the entire record
    • The following sections include writing some Apex code to determine whether methods should update or upsert
  • Update Data with the PUT Method
    • The upsertCase method added to the CaseManager class implements the PUT action
    • To invoke the PUT method:
      1. In Workbench REST Explorer, select PUT
      2. For the URI, enter /services/apexrest/Cases/
      3. Add the following code snippet to the request body, and replace <Record ID> with the ID of the case record created earlier
      4. Click Execute. This request invokes the upsertCase method, updating the Status, Subject, and Priority fields.
        • Note: the Status, Subject, and Priority fields are all updated. Since the request body didn’t contain a value for the Case Origin field, the origin parameter in the is null.
  • Update Data with the PATCH Method
    • An alternative to the PUT method is the PATCH method - it is used to update record fields
    • Multiple ways to use PATCH:
      • Specify parameters in the method of each field to update. Ex: create a methdod to update the priority of a case with this signature updateCasePriority(String priority). To update multiple fields, list all the desired fields as parameters.
      • Another approach is to pass the fields as JSON name/value pairs in the request body. This way, the method can accept an arbitrary number of parameters and the parameters aren’t fixed in the method’s signature.
        • updateCaseFields method adaded to the CaseManager class uses this approach. It deserializes the JSON string from the request body into a map of name/value pairs and uses the sObject put method to set the fields.
    • To invoke the PATCH method:
      1. In Workbench REST Explorer, select PATCH
      2. Enter URI /services/apexrest/Cases/<Record ID>, where <Record ID> should be replaced with the ID of the case record created earlier. Enter the JSON below in the Request Body.
      3. Click Execute. This request invokes the updateCaseFields method in your REST service. The Status and Priority fields of the case record are updated to new values.
{
  "status" : "Escalated",
  "priority" : "High"
}
  • Test Your Apex REST Class
    • Testing an Apex REST class is similar to testing any other Apex class - just call the class methods by passing in parameter values and then verify the results
    • For methods that don’t take parameters or that rely on information in the REST request, create a test REST request.
      • To simulate a REST request, create a RestRequest in the test method, and then set properties on the request as follows. You can also add params that you “pass” in the request to simulate URI parameters
// Set up a test request
RestRequest request = new RestRequest();
// Set request properties
request.requestUri =
    'https://yourInstance.my.salesforce.com/services/apexrest/Cases/'
    + recordId;
request.httpMethod = 'GET';
// Set other properties, such as parameters
request.params.put('status', 'Working');
// more awesome code here....
// Finally, assign the request to RestContext if used
RestContext.request = request;
  • If the method you’re testing accesses request values through RestContext, assign the request to RestContext to populate it (RestContext.request = request;)
    • To save the entire class and run the results:
      1. Dev Console > File > New > Apex Class, class name = CaseManagerTest then use the following
      2. Save the class
      3. Run all the tests in your org via Test > Run All. The test code coverage for the CaseManager class is 100%.
@IsTest
private class CaseManagerTest {
    @isTest static void testGetCaseById() {
        Id recordId = createTestRecord();
        // Set up a test request
        RestRequest request = new RestRequest();
        request.requestUri =
            'https://yourInstance.my.salesforce.com/services/apexrest/Cases/'
            + recordId;
        request.httpMethod = 'GET';
        RestContext.request = request;
        // Call the method to test
        Case thisCase = CaseManager.getCaseById();
        // Verify results
        System.assert(thisCase != null);
        System.assertEquals('Test record', thisCase.Subject);
    }
    @isTest static void testCreateCase() {
        // Call the method to test
        ID thisCaseId = CaseManager.createCase(
            'Ferocious chipmunk', 'New', 'Phone', 'Low');
        // Verify results
        System.assert(thisCaseId != null);
        Case thisCase = [SELECT Id,Subject FROM Case WHERE Id=:thisCaseId];
        System.assert(thisCase != null);
        System.assertEquals(thisCase.Subject, 'Ferocious chipmunk');
    }
    @isTest static void testDeleteCase() {
        Id recordId = createTestRecord();
        // Set up a test request
        RestRequest request = new RestRequest();
        request.requestUri =
            'https://yourInstance.my.salesforce.com/services/apexrest/Cases/'
            + recordId;
        request.httpMethod = 'DELETE';
        RestContext.request = request;
        // Call the method to test
        CaseManager.deleteCase();
        // Verify record is deleted
        List<Case> cases = [SELECT Id FROM Case WHERE Id=:recordId];
        System.assert(cases.size() == 0);
    }
    @isTest static void testUpsertCase() {
        // 1. Insert new record
        ID case1Id = CaseManager.upsertCase(
                'Ferocious chipmunk', 'New', 'Phone', 'Low', null);
        // Verify new record was created
        System.assert(Case1Id != null);
        Case case1 = [SELECT Id,Subject FROM Case WHERE Id=:case1Id];
        System.assert(case1 != null);
        System.assertEquals(case1.Subject, 'Ferocious chipmunk');
        // 2. Update status of existing record to Working
        ID case2Id = CaseManager.upsertCase(
                'Ferocious chipmunk', 'Working', 'Phone', 'Low', case1Id);
        // Verify record was updated
        System.assertEquals(case1Id, case2Id);
        Case case2 = [SELECT Id,Status FROM Case WHERE Id=:case2Id];
        System.assert(case2 != null);
        System.assertEquals(case2.Status, 'Working');
    }    
    @isTest static void testUpdateCaseFields() {
        Id recordId = createTestRecord();
        RestRequest request = new RestRequest();
        request.requestUri =
            'https://yourInstance.my.salesforce.com/services/apexrest/Cases/'
            + recordId;
        request.httpMethod = 'PATCH';
        request.addHeader('Content-Type', 'application/json');
        request.requestBody = Blob.valueOf('{"status": "Working"}');
        RestContext.request = request;
        // Update status of existing record to Working
        ID thisCaseId = CaseManager.updateCaseFields();
        // Verify record was updated
        System.assert(thisCaseId != null);
        Case thisCase = [SELECT Id,Status FROM Case WHERE Id=:thisCaseId];
        System.assert(thisCase != null);
        System.assertEquals(thisCase.Status, 'Working');
    }  
    // Helper method
    static Id createTestRecord() {
        // Create test record
        Case caseTest = new Case(
            Subject='Test record',
            Status='New',
            Origin='Phone',
            Priority='Medium');
        insert caseTest;
        return caseTest.Id;
    }          
}

  • The following worked example is an Apex REST class that is accessible at /Accounts/<Account_ID>/contacts The services returns the account’s ID and name plus the ID and name of all contacts associated with the account.
    • The test class that follows achieves 100% code coverage for the class

AccountManager.apxc

@RestResource(urlMapping='/Accounts/*/contacts')
global with sharing class AccountManager{
    @HttpGet
    global static Account getAccount(){
        RestRequest request = RestContext.request;
        String accountId = request.requestURI.substringBetween('Accounts/','/contacts');
        system.debug(accountId);
        Account objAccount = [SELECT Id,Name,(SELECT Id,Name FROM Contacts) FROM Account WHERE Id = :accountId LIMIT 1];
        return objAccount;
    }
}

AccountManagerTest.apxc

//Test class
@isTest 
private class AccountManagerTest{
    static testMethod void testMethod1(){
        Account objAccount = new Account(Name = 'test Account');
        insert objAccount;
        Contact objContact = new Contact(LastName = 'test Contact',
                                         AccountId = objAccount.Id);
        insert objContact;
        Id recordId = objAccount.Id;
        RestRequest request = new RestRequest();
        request.requestUri =
            'https://resourceful-narwhal-v8vop4-dev-ed.my.salesforce.com/services/apexrest/Accounts/'
            + recordId +'/contacts';
        request.httpMethod = 'GET';
        RestContext.request = request;
        // Call the method to test
        Account thisAccount = AccountManager.getAccount();
        // Verify results
        System.assert(thisAccount!= null);
        System.assertEquals('test Account', thisAccount.Name);
    }
}