6

I am practising the Integration workbook provided by salesforce. In tutorial#2 there's a class which makes future callouts to a Heroku app.This class is called by an after update trigger. Callout class:

public class UpdateHeroku {
 public class ExternalOrder{
        public string id{get;set;}
        public Integer order_number{get;set;}
    } 

    @future(callout=true)
    public static void postOrder(list<id> invoiceids){
        JSONGenerator gen = Json.createGenerator(true);
        gen.writeStartArray();
        for(id invoiceid:invoiceids){
            gen.writeStartObject();
            gen.writeStringField('id', invoiceid);
            gen.writeEndObject();
        }
        gen.writeEndArray();

        string jsonOrders = gen.getAsString();
        System.debug('jsonOrders: '+ jsonOrders);

        HTTPrequest req = new HTTPrequest();
        req.setMethod('POST');
        req.setEndpoint('https://dry-springs-6303.herokuapp.com');
        req.setHeader('Content-Type', 'application/json');
        req.setBody(jsonOrders);
        req.setTimeout(30000);

        Http http = new Http(); 
        HttpResponse res = http.send(req);
        System.debug('Fulfillment service returned '+ res.getBody());

        if(res.getStatusCode()!=200){
            System.debug('Error from '+ req.getEndpoint() + ':'+ res.getStatusCode() + ' ' + res.getStatus());
        }
        else
        {
            list<invoice__c> invoices = [select id from Invoice__c where id in :invoiceids];
            list<externalorder> orders = (list<ExternalOrder>)JSON.deserialize(res.getBody(), list<ExternalOrder>.class);

        Map<Id, Invoice__c> invoiceMap = new Map<Id, Invoice__c>(invoices);
        for(externalorder order:orders){
            Invoice__c invoice = invoiceMap.get(order.id);
            invoice.orderid__c = String.valueOf(order.order_number);
            }
            update invoices;
        } 
    }
}

I have written a test class for trigger which covers 62% of the callout class. I am figuring a way to separately unit test the callout class & wrote an implementation of mock interface

Test Class- which lead to code coverage of 66%

 @isTest
    public class UpdateHeroku_Test {
        @isTest static void testCallout(){
            Test.setMock(HttpcalloutMock.class, new MockGenerator_Test());

            list<invoice__c> invoices = new list<invoice__c>();
            list<id> invoiceids = new list<id>();
            invoices.add(new invoice__c(status__c='Negotiating'));
            invoices.add(new invoice__c(status__c='Pending'));
            insert invoices;
            invoices[0].status__c = 'Closed';
            invoices[1].status__c = 'Closed';
            update invoices;
            invoiceids.add(invoices[0].id);
            invoiceids.add(invoices[1].id);
            Test.startTest();
            UpdateHeroku.postOrder(invoiceids);
            Test.stopTest();
            System.assertEquals(expected1, invoice[0].orderid__c);
            System.assertEquals(expected2, invoice[1].orderid__c);
        }

global class MockGenerator_Test implements HttpCalloutMock{
        global httpResponse respond(HTTPrequest req){

            System.assertEquals('https://dry-springs-6303.herokuapp.com', req.getEndpoint());
            System.assertEquals('POST', req.getMethod());

            HttpResponse res = new HttpResponse();
            res.setHeader('Content-Type', 'application/json');
            res.setStatusCode(405);
            res.setStatus('Method Not Allowed');
            return res;
        }
      }

}

Since future methods can only return void type. I can't verify values received in HttpResponse using assertEquals method. Something like below which I intend to -

String contentType = res.getHeader('Content-Type');
System.assert(contentType == 'application/json');
String actualValue = res.getBody();
String expectedValue = '{"foo":"bar"}';
System.assertEquals(actualValue, expectedValue);

How can I verify the response returned? Thanks,

  • If you want to verify the response, write an additional test for that. If you're testing the future method, call it from the test and verify that it had the effect you intended. – rael_kid Feb 8 '15 at 10:35
  • @Lex could you please advise how? – Jarvis Feb 8 '15 at 13:22
  • I would advise you to do the call out in a separate function that just returns the deserialized response. Then use that function from your future method. Write a separate test for the function that does the callout, this test verifies the response. Then write a separate test that verifies the intended logic for the future method. – rael_kid Feb 8 '15 at 18:32
  • 1
    You will need to use the startTest and stopTest methods to invoke the future methods execution. Once you do that you test for the result by querying for the records that Should have been updated and asserting the values were actually updated accordingly. (Using the values returned from you mock implementation). Its a logic problem, think it through, what should happen then test to ensure it actually did – Eric Mar 8 '15 at 4:01
4

Here is where Test.startTest() and Test.stopTest() come in to play.

Structure your test to do all the data setup, then call startTest(). Execute your tested method and then call StopTest().

Among the many things this does, is force @future methods and callouts to fire. If you couple start/stopTest with mocked http callouts and you should be good to go.

  • Test.stopTest causes all enqueued asynchronous calls to complete. Neither this method nor Test.startTest really have anything to do with callouts, other than to put the DML in a separate transaction. – Adrian Larson Jul 4 '16 at 1:44
  • Adrian, While it's true that stopTest causes all async calls to complete, it's also true that a http callout made after startTest, will be forced to completion by stopTest. – Kevin P Jul 4 '16 at 16:58
  • Can you back that up with documentation? Answers are always improved by citing a credible source. – Adrian Larson Jul 4 '16 at 17:01
  • Write a unit test that exercises a http callout and you'll see that it forces the callout to return. – Kevin P Jul 5 '16 at 13:29
  • 1
    I'm not the one who answered the question! – Adrian Larson Jul 5 '16 at 14:26
3

If you are calling a future method, it is a asynchronous call. Asynchronous call run using different thread in system so the result of the method can never be returned to calling method. But this is what required in test method as we are required to assert the values after completion of the call in this situation Test.start() and Test.stop() methods come for rescue.

In this particular case a asynchronous method is making another asynchronous call. Test.start() and Test.stop() can only keep track of a single asynchronous method. so its not possible to get the data returned by inner asynchronous call.

This is known issue and you need to test the callout separately.

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.