0

I'm struggling to test this situation, I'm new to salesforce and I need to integrate SF with our website, whenever someone update/create/delete account on SF I should send that information to our website. I created three separated trigger:

trigger beforeDelete on Account (before Delete){
  SendNotification.process(trigger.new, 'delete');
}


trigger afterUpdate on Account (after update) {
  SendNotification.process(trigger.new, 'update');
}

trigger afterInsert on Account (after insert) {
  SendNotification.process(trigger.new, 'create');
}

This class has two methods, one creates a JSON from List that I got from a trigger, and another one sends that JSON.

public with sharing class SendNotification {

    public static void process(List<SObject> arrayOfObjects, String action, String objectName){
        sendToApp(createJSON(arrayOfObjects, action));
    }

    public static String createJSON(List<SObject> arrayOfObjects, String action){
        JSONGenerator gen = JSON.createGenerator(true);
        gen.writeStartObject();
        gen.writeFieldName('values'); 
        gen.writeStartArray();
        for( integer i = 0; i < arrayOfObjects.size(); i++){
            gen.writeStartObject();
            gen.writeStringField('id', arrayOfObjects[i].Id);
            gen.writeStringField('Name', arrayOfObjects[i].Name);
            gen.writeStringField('action', action);
            gen.writeEndObject();
        }
        gen.writeEndArray();
        gen.writeEndObject();
        String pretty = gen.getAsString();

        return pretty;
    }

    @future (callout=true)
    public static void sendToApp(String body) {
        Http http = new Http();
        HttpRequest request = new HttpRequest();

        request.setEndpoint('https://our_web/our_api');
        request.setMethod('POST');
        request.setHeader('Content-Type', 'application/json;charset=UTF-8');
        request.setBody(body);

        if ( !Test.isRunningTest() ){
            HttpResponse response = http.send(request);

            if(response.getStatusCode() != 200){
                System.debug('ERROR');
                System.debug('Res returned');
                System.debug(response.toString()); 
            }else {
                System.debug('Res returned');
                System.debug(response.toString()); 
            }
        }
    }
}

This is my test class:

    @isTest
public with sharing class SendNotificationTest{

    @istest static void json(){
        Account acc= new Account(Name='Test Account');
        insert acc;
        List<SObject> myList = new List<SObject>();
        myList.add(acc);

        string s = SendNotification.createJSON(myList, 'create');
        s = s.replaceAll('\n', '').replaceAll('\r','');
        s = s.deleteWhitespace();

        string expected='{"values":[{"id":"'+acc.Id+'","Name":"'acc.Name'","action":"create"}]}';
        System.assertEquals(expected,s);
    }

    @istest static void deleteTrigger(){
        Account acc= new Account(Name='Test Account');
        insert acc;

        Test.startTest();
        delete acc;
        Test.stopTest();
        System.debug('This is sistem getCallouts delete');
        System.debug(Limits.getCallouts());
        System.assertEquals(Limits.getFutureCalls(), 1);
    }

    @istest static void updateTrigger(){
        Account acc= new Account(Name='Test Account');
        insert acc;

        Test.startTest();
        update acc;
        Test.stopTest();
        System.assertEquals(Limits.getFutureCalls(), 1);
    }

    @isTest static void testPostCallout() {
        Notifier.sendNotification('1');
        System.assertEquals(Limits.getFutureCalls(), 1);
    }
}

The first test, JSON pass successfully and the last one(testPostCallout).

But I have a problem with other tests:

System.assertEquals(Limits.getFutureCalls(), 1); it's not 1, it looks like when Test.startTest() and Test.stopTest() finished, they don't restart Limits.getFutureCalls(), other tests fails, because Limits.getFutureCalls() is greater then 1.

If I try Limits.getCallouts() is allways 0, why is that?

How can I restart Limits? Is this a good way to test triggers?

Thanks.

2
  • 1
    Have you considered using Change Data Capture rather than using custom code to achieve this objective? – David Reed Apr 16 '20 at 17:25
  • You should pretty much never use Test.isRunningTest, and definitely not in this way. Read up on HttpMock. – Adrian Larson Apr 16 '20 at 17:31
2

There are two vital changes you need to make if you wish to cover your callout.

  1. Never use Test.isRunningTest. Use mocks instead here.
  2. Create a synchronous method you call from your @future method.

Mocking

First of all, remove your check on Test.isRunningTest and just run everything inside that block unconditionally. Then in your tests, you'll need to set a few different mocks to cover your various status code conditions.

class CalloutMock extends HttpCalloutMock
{
    final Integer statusCode;
    HttpCalloutMock(Integer statusCode)
    {
        this.statusCode = statusCode;
    }
    public HttpResponse respond(HttpRequest request)
    {
        HttpResponse response = new HttpResponse();
        response.setStatusCode(statusCode);
        // set any other attributes as desired
        return response;
    }
}
@IsTest static void testMyCallout_200()
{
    Test.setMock(HttpCalloutMock.class, new CalloutMock(200));

    // set up data as necessary

    Test.startTest();
        // call method
        // cache any limits checks here
    Test.stopTest();

    // assert as necessary
}
// add tests for other status codes

Synchronous Method

Creating a synchronous method which your @future method calls allows you to actually inspect limits consumption.

@future
public static void makeAsyndhronousCallout(String payload)
{
    makeSynchronousCallout(payload);
}
public static void makeSynchronousCallout(String payload)
{
    // actual logic here
}

Once restructured in this way, you can have your trigger tests verify you consume a future call, and have a test against the synchronous method which verifies you actually consume a callout.


Apart from the above, I recommend one other change. While it is not vital, it is much more convenient, includes a notable boost to performance, and makes it easier to avoid null pointers and some other errors.

When working with JSON, I highly recommend you to work with serialization/deserialization rather than using generators/parsers. You just need to create a model class to serialize from.

class BulkPayload
{
    final List<SinglePayload> values;
    public BulkPayload(List<SObject> records, String action)
    {
        values = new List<SinglePayload>();
        for (SObject record : records)
            values.add(new SinglePayload(record, action));
    }
}
class SinglePayload
{
    final String id, name, action;
    Payload(SObject record, String action)
    {
        this.id = record.Id;
        this.name = (String)record.get('Name');
        this.action = action;
    }
}

Now your entire createJSON method can be simplified to:

return JSON.serialize(new BulkPayload(arrayOfObjects, action));
1
  • @adrianLasrson Hey, thank you very much, I will try to implement those changes. Once again thanks for the help :) – Mike Apr 24 '20 at 7:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.