9

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,

4
  • 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. Commented Feb 8, 2015 at 10:35
  • @Lex could you please advise how?
    – Jarvis
    Commented Feb 8, 2015 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. Commented Feb 8, 2015 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
    Commented Mar 8, 2015 at 4:01

4 Answers 4

7

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.

5
  • 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
    Commented Jul 4, 2016 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
    Commented Jul 4, 2016 at 16:58
  • Can you back that up with documentation? Answers are always improved by citing a credible source.
    – Adrian Larson
    Commented Jul 4, 2016 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
    Commented Jul 5, 2016 at 13:29
  • 1
    I'm not the one who answered the question!
    – Adrian Larson
    Commented Jul 5, 2016 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.

0

Thank you Kevin.

I have the same issue and figure out a working code sample as below:

Test.startTest();
do_the_update_with_future_call();
Test.stopTest();

List<Account> checkAccounts = [ .... ]
System.assertEquals(....)

The result's query must be set after the "Test.stopTest()"!

If the query is set between startTest and stopTest then the test will be failed due to async process.

Below code will make the same testing failed.

Test.startTest();
do_the_update_with_future_call();;
List<Account> checkAccounts = [ .... ]
Test.stopTest();

System.assertEquals(....)
0

As the callout isn't actually done, it's a bit pointless testing what it returns! All the data returned by the HttpResponse is what you set in the Mock class.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .