3

I have a Queueable class MyQueueableClass which in turns calls an outbound web service class.

public void execute(QueueableContext qc){
    try{
        GlobalCallout.makeCallout(caseId);
    } catch (Exception ex){
        System.debug('Exception: ' +ex);}
  }

And in class: GlobalCallout we are making a call out to an external service and performing other stuff. While writing a test class for the above Queueable Class, my call out is getting failed, as I am familiar with: We cannot perform an actual call out in test class. So, I picked a different route: Creating mock data for the class: GlobalCallout and right before the mock call out, I have System.enqueueJob(new MyQueueableClass(caseSC.Id)); assuming it would get the mock data from the context.

When I ran the test class, I get System.HttpResponse[Status=null, StatusCode=0]. Can someone tell me if I am doing it wrong? If my approach is not valid, can I get some insights on making it right?

My implementation for test class:

@isTest
public class MyQueueableTestClass{
static HttpResponse response;
Static String successData= 'Success';
Static String failureData= 'Faied';

@testSetup static void testData() { 

    //Create a test case record
}

public class MockSuccess implements HttpcalloutMock {
    public HTTPResponse respond(HTTPRequest req) {
                response = new HttpResponse();
                response.setbody(successData);
                response.setstatuscode(200);
                return response;
        }
}

static testmethod void testForSuccess(){
    Case caseSC = [Select Id From Case Limit 1];
    Test.setMock(HttpCalloutMock.class, new MockSuccess());
    Test.startTest();
    System.enqueueJob(new MyQueueableClass(caseSC.Id));
    System.debug('Response::'+response);
    Test.stopTest();
 }
}

I am getting the debug log as:

Response::null

Also, the debug for response within the web service call out class:

System.HttpResponse[Status=null, StatusCode=200]

I am sure, I might be missing something very small.

  • Are you using Test.setMock in your unit test? – sfdcfox Jul 9 at 23:32
  • Hi @sfdcfox, yes, I am using it already. Still getting the statusCode=0 – Austin Evans Jul 10 at 3:54
  • Testing callouts with mocks is detailed in this developer guide on Salesforce: developer.salesforce.com/docs/atlas.en-us.apexcode.meta/… Additionally I think your mock will need to be in its file (top-level class) and not a subclass of your test. – nbrown Jul 16 at 18:13
  • Hi @nbrown, I had a similar implementation (mock class inside my test class) for another web service call out test class and it's working as expected. I don't think that's the issue. – Austin Evans Jul 16 at 18:35
  • 1
    In the past, I received "uncommitted work pending" errors in this scenario because enqueuing the job counts as a DML operation. You're not getting those errors now, but it still could be causing this unexpected result. You might need to test the queueable method and callout method separately as explained in the linked answer. – David Cheng Jul 23 at 15:01
1

before running the test you need to call the Test.setMock() Do something like this

First create a httpmock class

global class YOURMOCKCLASS implements HttpCalloutMock
{
  global HttpResponse response(HttpRequest req)
    {
        HttpResponse res = new HttpResponse();
        res.setHeader({});
        res.setBody({});
        res.setStatusCode(200);
        return res;
    }

}

Then call that class from your test class.

Test.setMock(HttpCalloutMock.class, new YOURMOCKCLASS());
Test.startTest();
...
...
Test.stopTest();
| improve this answer | |
1

It seems like you are implementing you Mock inside of the Test class itself. I would create it as another class instead, as @Aks is suggesting although it does not need to be global, but public. Also note that testMethod keyword is deprecated, you should use this instead:

@IsTest static void testForSuccess(){}

Additionally from my point of view (I do not know which logic does your main class involve) you should not check response status, but the logic that your class follows whether the statusCode and body are the expected or not.

| improve this answer | |
  • As the response== null, I am not getting any response.getBody(). Thus, I am unable to perform any assertions. – Austin Evans Jul 16 at 18:33
  • Updating to @isTest haven't changed the outcome. I am still having the exact same issue. – Austin Evans Jul 16 at 21:27
0

Following points (specific to apex test class execution) might give you a better insight into what is going on here. Static variables in an apex test class are reset between:

  1. every test method execution.
  2. every transaction boundary (specific to the apex jobs or future calls inside test class).

Salesforce does this in order to maintain truly independent testing for each method or transaction boundary. I couldn't find out salesforce documentation on point #2 specific to the scenario you have mentioned, but this complies to the way async code is executed in apex. Also, I have tested out this scenario in the past, which led me to this understanding.

In your code, the Test.startTest() method starts an additional execution context (with a fresh set of limits) and apex test execution starts keeping a tab on all the async method calls. When Test.stopTest() is executed, it triggers all the async method calls or jobs to be executed synchronously. In this case, since it calls a queueable job (which I'm assuming in turn calls a future method), the code executes in its own transaction boundary. So, completion of this code execution would reset the static variable to the original context. I believe that the internal mechanism on how exactly this happens is something Salesforce should answer.

Now, in your code, the debug statement to check the response value is declared right before the Test.stopTest(). This implies that the test execution hasn't yet called the callout method i.e., the mock http respond method hasn't been called. So, the response value is null as declared in the original context. Even if you move this debug statement below the Test.stopTest(), you won't be able to check it's value because the test execution would have reset it to the original value.

Inside the web service call out class, you get the following response value because (in your mock http respond method) you are not setting the status value but only the statusCode.

System.HttpResponse[Status=null, StatusCode=200]

So, your mock http callout class and its methods are working fine, but the usage of static variables is not the right approach in this specific test assertion or verification (explicitly because the callout happens thru an queueable apex). If you were to test the same callout synchronously called via a separate class (just for testing purpose as I don't recommend it for production scenario), you will find that the static response variable retains the values, since the test execution stays within the same transaction boundary.

As others have pointed out,

  • Write test assertions based on the actual outcome and not the http response value.
  • Write Mock Http call out class as a separate class and not inside the test class (although the platform won't stop you from doing it). The purpose of this class is to mock the callout and not actually test the response status from the actual external service. So, as long as the correct parameters are passed to the Test.setMock method and the HttpResponse is correctly populated in the mock http callout implementation, the call out response would always be successful.

Hope this helps.

| improve this answer | |
0

A little addition to the recommendations of the previous responders:

  • Please, use assertions or debugs after Test.stopTest() since until it is called your enqueued job will not finish. In other words, the job hasn't finished yet at the line of your debug call.

I've faced the queueables that perform DMLs after the callout finishes, and about those ones, I can say that they worked correctly.

| improve this answer | |
-1

I'm not sure if something wrong in your code/org (didn't find any) or its core issue in SF related to Queueable & setMock.

The simple solution will be to use Test.isRunningTest() before your http.send. You can either construct dummy response there or you can use your mock class to get the test response. Probably something like:

HttpReponse res = Test.isRunningtest() ? new MockSuccess().response(req) : http.send(req);
| improve this answer | |
-1

You need to define mock request & response to test all Callout related scenarios (200, 404, 406, 500 etc)

Implement HttpCalloutMock to determine what kind of response your code should get

@isTest
public class MyHttpMockClass implements HttpCalloutMock{
    // map<url, response>
    public Map<String, HttpResponse> responseMap;
    // map<url, true/false>, you only need to set true for urls where you want to test exception cases
    public Map<String, Boolean> sendExceptionMap;
    //add more functions/variables as per your requirement
    public HttpResponse respond(HttpRequest request){
        if(request.getEndPoint() == 'the endpoint i wanna test'){
            // use responseMap for cleaner code
            HttpResponse response = new HttpResponse();
            //create response body here or in a helper method
            response.setHeader('Content-Type', 'application/json');
            response.setBody('{"field":"value"}');
            response.setStatus('success');
            response.setStatusCode(200);
            return response;
            // you can create class level map to store responses for 200, 404, etc
            // and use that map & another variable to determine when to send what response
        }
    }
}

Set all these values before Test.startTest()

@isTest
public static void test_fn(){
  // some logic, or data creation
  MyHttpMockClass mobk_obj = new MyHttpMockClass();
  // set responses
  Test.startTest();
  Test.setMock(HttpCalloutMock.class, mock_obj);
  //you method call
  Test.stopTest();

  //Assert Results
}

Hope this helps.

Since a queueable class doesnt return values, you can't directly get HttpResponse by executing Queueable class. In a real-life scenario, a queueable class will make a callout, get response and update/create/delete a record. You can create response in such a way that the data is affected in an expected way. Then after Test.stopTest, you can assert that indeed target record(s) have been modified/created/deleted.

This way you can test callout that happens in a Queueable class

| improve this answer | |
-2

similar to this reference below can you try keeping your Test.setMock after Test.StartTest

Test.startTest();
Test.setMock(HttpCalloutMock.class, mock_obj);
// your method call
Test.stopTest();

FYR.. https://www.thephani.com/test-classes-for-future-methods-callout-true/

| improve this answer | |

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.