1

I am trying to understand the behavior of testing Queueable classes, most specifically asserting that an exception was thrown in an execution.

To provide some context: I have a Queueable Class (QueueableExample), whose execute() method calls a second method called testMethod(). Something similar to this:

public without sharing class QueueableExample implements Queueable, Database.AllowsCallouts {
   String origin;
   String params;

   public QueueableExample(String origin, String params) {
        this.origin = origin;
        this.params = params;
    }

    public void execute(System.QueueableContext context) {
        testMethod(origin, params);
    }


    public static void testMethod(String origin, String params){
        List<MyInnerWrapper> test = (List<MyInnerWrapper) JSON.deserialize(params, List<MyInnerWrapper>.class);
    }

Now, I am trying to cover a scenario in which passing an empty ('') string as the second argument should trigger an exception (as the deserialization will fail).

The issue I am facing is that, in the test class where the test is being made, if System.enqueueJob() is executed and the deserialization fails the whole test method fails as well, whereas if I specifically call testMethod() then the exception is caught as expected.

This one test method passes:

@IsTest
static void testQueueable_InvalidRequest(){
    try {
    Test.startTest();
    QueueableExample.testMethod('origin', '');
    Test.stopTest();
    } catch (Exception e) {
        System.debug(e.getTypeName());
    }
}

But this one fails:

@IsTest
static void testQueueable_InvalidRequest(){
    try {
    Test.startTest();
    System.enqueueJob(new QueueableExample('origin', ''));
    Test.stopTest();
    } catch (Exception e) {
        System.debug(e.getTypeName());
    }
}

with the entire test method failing with a System.JSONException: No content to map to Object due to end of input exception (which is exactly what I am trying to assert was thrown).

16:22:03.301 (3354101547)|FATAL_ERROR|System.JSONException: No content to map to Object due to end of input

(System Code)
Class.QueueableExample.testMethod: line 33, column 1
Class.QueueableExample.execute: line 21, column 1

While I understand that all asynchronous execution runs after the Test.stopTest() block, shouldn't the fact that all the code is wrapped on a try-catch block prevent the test method itself from failing?

Thank you in advance for the help!

4
  • 1
    What is the specific error that you're getting (please include the entire error and stack trace, verbatim)? Do you get the same error if you modify that test method to call the execute() method directly (i.e. new QueueableExample('origin', '').execute(null);, in 99% of cases, you can just pass null in for QueueableContext/BatchContext)?
    – Derek F
    Feb 20, 2023 at 15:10
  • @DerekF thanks for the help! Including the stack trace on the original post and calling the execute() method directly also works, so it seems mostly tied to the System.enqueueJob() method, but I cannot work out why that would be the case. Feb 20, 2023 at 15:27
  • 2
    Hmm, I'm not seeing anything when I search for known issues. I'd argue that your test shouldn't be using System.enqueueJob(); because we can't make changes to how Salesforce schedules jobs and should trust that Salesforce tests this themselves (you're interested in testing that execute() behaves correctly, and you can directly call that method). If that's good enough for you, then that's how I'd proceed. Otherwise, this is probably something you'll need raise a support ticket with Salesforce for (though I do hesistate to suggest someone inflict that process on themselves).
    – Derek F
    Feb 20, 2023 at 15:46
  • I do agree with you that my goal is to test the execute method indeed. It will work for me by invoking it with null as you mentioned, so thank you for that suggestion! Feb 20, 2023 at 16:04

1 Answer 1

1

I would reframe your approach here, as it seems you are missing out on one of the biggest benefits of the clean architecture you put in by having execute just call a separate method. You can load all of your testing granularity on that separate method, which has much lower overhead. Your test to enqueue is basically just verifying system behavior. It's low value, but gives you that coverage metric. All other testing rigor belongs on that separate method, which makes your question moot.

1
  • True, you're right about the fact that the testing "juice" is on a separate method. Might just be the force of habit that I am used to doing the whole enqueueing (which after yours and Derek's comment I realized it might be a bad practice in the sense I am not only testing my code but system behavior like you said),but your comment changed my perception about testing async Apex, so thanks for the clear explanation. Feb 20, 2023 at 16:20

You must log in to answer this question.

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