Search code examples
salesforceapexapex-codesalesforce-lightning

Issue deploying change set in salesforce prod - code coverage issue


Being new to salesforce i'm trying to make a prod deployment but it is failing because of code coverage issue. These are the apex classes that i wrote:

HttpCallOut.apxc

public with sharing class HttpCallOut {
    @future(callout=true)
    public static void callService(string endpoint, string method, string body) {
        try {
            Http http = new Http();
            HttpRequest request = new HttpRequest();
            request.setEndpoint('callout:API_SERVICES' + endpoint);
            request.setMethod(method);
            request.setHeader('content-type', 'application/json');
            request.setBody(body);
            
            HttpResponse response = http.send(request);
            
            if (response.getStatusCode() != 200) {
                System.debug('The status code returned was not expected: ' + response.getStatusCode() + ' ' + response.getStatus() + ' ' + response.getBody());
                throw new CustomException('Error calling endpoint ' + endpoint + 'Status Code: ' + response.getStatusCode() + 'Status: ' + response.getStatus()+ 'Response Body: ' + response.getStatus());
            }
            return;
        } catch (CalloutException  calloutException) {
            throw new CustomException('Error in HttpCallOut callService: ', calloutException);
        }
         
    }

}

CustomException.Apxc

public class CustomException extends Exception {

}

HandleUpdateOnReturnRefundCase

public with sharing class HandleUpdateOnReturnRefundCase {
    private HttpCallOut httpCallOut = new HttpCallOut();
    @InvocableMethod
    public static void handleUpdateOnReturnRefundCase(List<Id> caseIds) { 
        List<Case> updatedCases = [select id, Decision__c, ZoroCaseIdentifier__c, Type, Description, ReturnOptions__c from Case where Id in :caseIds];
        Case updatedCase = updatedCases.get(0);

        if((updatedCase.Decision__c == 'Approved' && updatedCase.ReturnOptions__c != null) || updatedCase.Decision__c == 'Rejected') {
            httpCallOut.callService(
                '/salesforce-case-updated',
                 'POST',
                 genPayload(updatedCase.id, updatedCase.ZoroCaseIdentifier__c, updatedCase.Type)
            );
         } else {
             throw new CustomException('Cannot update the case, please fill all required fields');
         }
        update updatedCases;
    }
   
    private static String genPayload(Id caseId, String zoroCaseIdentifier, String caseType) {
        CasePayload casePayload = new CasePayload();
        casePayload.caseId = caseId;
        casePayload.caseType = caseType;
        casePayload.zoroCaseIdentifier = zoroCaseIdentifier;
        return JSON.serialize(casePayload);
    }
}

CasePayload.Apxc

public class CasePayload {
    public Id caseId {
      get { return caseId; }
      set { caseId = value; }
   }
    
    public String zoroCaseIdentifier {
      get { return zoroCaseIdentifier; }
      set { zoroCaseIdentifier = value; }
   }
    
   
    public String caseType {
      get { return caseType; }
      set { caseType = value; }
   }
}

For above apex classes i have written test cases:

HandleUpdateOnReturnRefundCaseTest

@isTest
private class HandleUpdateOnReturnRefundCaseTest {
    
    static testMethod void testApprovedReturnRefundCase() {
        Case mockCase = new Case();
        mockCase.Type = 'Request Return';
        insert mockCase;
        
        Test.startTest();
        Test.setMock(HttpCalloutMock.class, new HttpCalloutSuccessTest());
        mockCase.Decision__c = 'Approved';
        mockCase.ReturnOptions__c = 'FOC without Returned Items';
        update mockCase;
        Test.stopTest();
        
        List<Case> cases = [SELECT Id, Decision__c FROM Case WHERE Id = :mockCase.Id];
        for (Case c : cases) {
            system.assertEquals(c.Decision__c, mockCase.Decision__c);
        }
    }
    
    static testMethod void testRejectedReturnRefundCase() {
        Case mockCase = new Case();
        mockCase.Type = 'Request Return';
        insert mockCase;
        
        Test.startTest();
        Test.setMock(HttpCalloutMock.class, new HttpCalloutSuccessTest());
        mockCase.Decision__c = 'Rejected';
        update mockCase;
        Test.stopTest();
        
        List<Case> cases = [SELECT Id, Decision__c FROM Case WHERE Id = :mockCase.Id];
        for (Case c : cases) {
            system.assertEquals(c.Decision__c, mockCase.Decision__c);
        }
    }
    
   static testMethod void testCaseApprovedWithoutReturnOptions() {
        try 
        {
          Case mockCase = new Case();
          mockCase.Type = 'Request Return';
          insert mockCase;
            
          Test.startTest();
          Test.setMock(HttpCalloutMock.class, new HttpCalloutSuccessTest());
          mockCase.Decision__c = 'Approved';
          mockCase.ReturnOptions__c = null;
          update mockCase;
          Test.stopTest();
         } catch (Exception e) {
              System.assert(e.getMessage().contains('Please select Return Options before saving'), 'message=' + e.getMessage());
         }  
    }
    
    static testMethod void testCaseUpdatedWithHttpError() {
        try
        {
            Case mockCase = new Case();
            mockCase.Type = 'Request Return';
            insert mockCase;
            Test.startTest();
            Test.setMock(HttpCalloutMock.class, new HttpCalloutErrorTest());
            mockCase.Decision__c = 'Approved';
            mockCase.ReturnOptions__c = 'FOC without Returned Items';
        
            update mockCase;
            Test.stopTest();
            
        } catch (Exception e) {
             System.assert(e.getMessage().contains('Error calling endpoint /salesforce-case-updated'), 'message=' + e.getMessage());
        } 
    } 
}

HttpCalloutSuccessTest

global class HttpCalloutSuccessTest implements HttpCalloutMock{
  global HttpResponse respond(HTTPRequest req){
    HttpResponse res = new HttpResponse();
    res.setHeader('Content-Type', 'application/json');
    res.setBody('{"status":"success"}');
    res.setStatusCode(200);
    return res;
  }
}

HttpCalloutErrorTest

@isTest
global class HttpCalloutErrorTest implements HttpCalloutMock{
  global HttpResponse respond(HTTPRequest req){
    HttpResponse res = new HttpResponse();
    res.setStatusCode(500);
    return res;
  }
}

When i run the test in sandbox by clicking "Run ALL Test" - i see that cove coverage is 92%. SO i made outbound set and tried to deploy to prod.

But in prod when i validated inbound change set it complained about code code coverage issue.

Not sure what i'm doing wrong? If test cases are missing then for which part of the code?

enter image description here


Solution

  • So tests passed, no exceptions, "just" code coverage problem? You included the tests in the changeset?

    This looks like it's supposed to be called from a Flow/ProcessBuilder (InvocableMethod). Did you include that flow in package? Is it active? Or Case trigger (if it's called from trigger)

    Or make the unit test call HandleUpdateOnReturnRefundCase.handleUpdateOnReturnRefundCase() directly, without "firing mechanism". It's an unit test, not a whole system integration. Test blocks independently and composition separately, will help you nail any errors.

    If you still can't figure it out - refresh new sandbox, deploy to it (without unit test run) and continue working there, examine debug logs etc.