Thursday, July 6, 2017

Using Database.upsert with external ID field

External Id plays very important role if you want to update records without knowing the record Ids or want to relate the child record with parent record without knowing the parent record Id.

As a best practice, you should always make External Id unique. If you are performing upsert with External Id, then following situations will occur:
  1. If no record is found in table with provided External Id, then it will create record in table.
  2. If 1 record is found in table with provided External Id, then it will update record in table.
  3. If more than 1 records is found in table with provided External Id, then system will throw an error.

I am going to cover 2 different aspect of using external Id in apex.
  • Updating a record with External Id

Create a External Id field on Account as Account_Unique_Number__c and mark it as External Id and unique while creating it.Now we will create a new record using upsert.

Execute below command in developer console

List<Account> acclist=new list<Account>();
acc.name='Demo test1';
acc.Account_Unique_Number__c='00001';
acclist.add(acc);
Schema.SObjectField ftoken = Account.Fields.Account_Unique_Number__c;
Database.UpsertResult[] srList = Database.upsert(acclist,ftoken,false);
for (Database.UpsertResult sr : srList) {
    if (sr.isSuccess()) {
        // Operation was successful
    }
    else {
        // Operation failed, so get all errors                
        for(Database.Error err : sr.getErrors()) {
            System.debug('error has occurred.' + err.getStatusCode() + ': ' + err.getMessage());                    
            System.debug('fields that affected this error: ' + err.getFields());
            
        }
    }
}

As there is no record in Account with Account_Unique_Number__c as 00001, system will create a new record.

Now again we will run same script in developer console and will specify some more field values:

List<Account> acclist=new list<Account>();
Account acc=new Account();
acc.name='Demo test1';
acc.Account_Unique_Number__c='00001';
acc.type='Other';
acc.Industry='Banking';
acclist.add(acc);
Schema.SObjectField ftoken = Account.Fields.Account_Unique_Number__c;
Database.UpsertResult[] srList = Database.upsert(acclist,ftoken,false);
for (Database.UpsertResult sr : srList) {
    if (sr.isSuccess()) {
        // Operation was successful
    }
    else {
        // Operation failed, so get all errors                
        for(Database.Error err : sr.getErrors()) {
            System.debug('error has occurred.' + err.getStatusCode() + ': ' + err.getMessage());                    
            System.debug('fields that affected this error: ' + err.getFields());
            
        }
    }

Now you will see that system will update the record as it was able to find a Account record with Account_Unique_Number__c as 00001

  • Relating a child record with parent record by using parent record Id

In order to understand this, we will create contact record and will relate to account using Account_Unique_Number__c. Execute below code in developer console:

List<Contact> conlist=new list<Contact>();
Contact con=new Contact();
con.lastname='Kumar';
con.Firstname='Kumar';
con.email='sunil02kumar@gmail.com';
Account acc=new Account(Account_Unique_Number__c='00001');
con.Account=acc;
conlist.add(con);
Database.UpsertResult[] srList = Database.upsert(conlist,false);
for (Database.UpsertResult sr : srList) {
    if (sr.isSuccess()) {
        // Operation was successful
    }
    else {
        // Operation failed, so get all errors                
        for(Database.Error err : sr.getErrors()) {
            System.debug('error has occurred.' + err.getStatusCode() + ': ' + err.getMessage());                    
            System.debug('fields that affected this error: ' + err.getFields());
        }
    }
}

This will create a new contact for Account which have Account_Unique_Number__c as 00001.

In above code snippet, you can see that in order to relate contact with account, we are not specifying the account 15 or 18 digit record id. We are just specifying the external Id of account and system will maintain the relationship.

Why it is recommended to mark External Id as unique?

Imagine you are creating a contact and specified External Id of parent. Suppose there are 2 records in account table with same value, then system will not able to identify with whom it needs to relate the contact and will throw error saying more than 1 match found.

Same is applicable when you update the record with External Id.

Different ways of testing HTTP callout in apex

As we all know that test methods do not support HTTP callout, so all test method performing callout will fail.

In order to avoid the test class failure, we mainly use Test.IsRunningTest method in apex class. By using this method, we make sure that particular block of code performing HTTP callout should not run when called by test class methods.
if(!Test.isRunningTest()){
// apex code for HTTP callout
}
but this approach will reduce your code coverage. As per salesforce, you need to have atleast 75% coverage for all your apex code.

We will now go through different options through which we can test HTTP callout and increase our code coverage.
  • Using static resource
You can store the response of your HTTP callout in text file and upload it static resource. Now you can use built in apex class StaticResourceCalloutMock or MultiStaticResourceCalloutMock to build mock response and get response from static resource.

First create an instance of StaticResourceCalloutMock:

StaticResourceCalloutMock mockCallout = new StaticResourceCalloutMock();
mockCallout.setStaticResource('StaticResourceForCallout');
mockCallout.setStatusCode(200);
mockCallout.setHeader('Content-Type', 'application/json');

Now set mock callout mode in test method by using below method:

Test.setMock(HttpCalloutMock.class, mockCallout );

After this call method which perform callout. As we have set mock test callout, apex will not perform callout and will return response from static resource.

Note: MultiStaticResourceCalloutMock helps you test different HTTP callout with different endpoint URL. you can create instance of this class and specify callout response in different static resource for different endpoints and then set this as mock callout in test method.
  • Generating mock response in test by implementing the HttpCalloutMock Interface 
Create a apex class which implements HttpCalloutMock interface. In this interface, you can specify response which will be returned if test method perform callout.

@isTest
global class HTTPMockCallout implements HttpCalloutMock {
    global HTTPResponse respond(HTTPRequest req) {
        // specify the response here
        // return response.
    }

Note:
       1. Class should be public or global which implements HttpCalloutMock
       2. You can use @IsTest on this class as this will be used only through test class. In this way it                   will not count against organization code size limit.

Now you can set mock response in test method before calling method which perform HTTP callout.

Test.setMock(HttpCalloutMock.class, new HTTPMockCallout());

Below is sample code to provide code coverage to "CalloutUtility" apex class using both approaches mentioned above

public class CalloutUtility {
//method to retrieve information about the Salesforce version
public static HttpResponse performCallout() {
HttpRequest req = new HttpRequest();
req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionID());
req.setHeader('Content-Type', 'application/json');
String domainUrl=URL.getSalesforceBaseUrl().toExternalForm();
system.debug('********domainUrl:'+domainUrl);
req.setEndpoint(domainUrl+'/services/data/');
req.setMethod('GET');
Http h = new Http();
HttpResponse res = h.send(req);
system.debug(res.getBody());
return res;
}
}
view rawCalloutUtility.cls hosted with ❤ by GitHub
@isTest
private class CalloutUtilityTest1 {
testMethod static void unittest1() {
StaticResourceCalloutMock mockCallout = new StaticResourceCalloutMock();
mockCallout.setStaticResource('StaticResourceForCallout');
mockCallout.setStatusCode(200);
mockCallout.setHeader('Content-Type', 'application/json');
// Set the mock callout mode
Test.setMock(HttpCalloutMock.class, mockCallout);
// Call the method that performs the callout
HTTPResponse res = CalloutUtility.performCallout();
System.assertEquals(200,res.getStatusCode());
System.assertEquals('application/json', res.getHeader('Content-Type'));
}
}
@isTest
private class CalloutUtilityTest2 {
testMethod static void testCallout() {
// Set mock callout class
Test.setMock(HttpCalloutMock.class, new HTTPMockCallout());
// from the class that implements HttpCalloutMock.
HttpResponse res = CalloutUtility.performCallout();
// Verify response received contains fake values
String contentType = res.getHeader('Content-Type');
System.assert(contentType == 'application/json');
System.assertEquals(200, res.getStatusCode());
}
}
view rawCalloutUtilityTest2 hosted with ❤ by GitHub
@isTest
global class HTTPMockCallout implements HttpCalloutMock {
global HTTPResponse respond(HTTPRequest req) {
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'application/json');
string jsonResBody = '{ "label": "Spring \'17", "url": "/services/data/v39.0","version": "39.0"}';
res.setBody(jsonResBody);
res.setStatusCode(200);
return res;
}
}
view rawHTTPMockCallout.cls hosted with ❤ by GitHub