4

This question definitely is a duplicate of many questions here, but after reading most of them I am still somewhat clueless.

Question 1: why I can't just catch exceptions in the execute and send an email out? Would a) Batch help, or b) checking or c) writing a custom Error object?

public class MyQueue implements Queueable, Database.AllowsCallouts {

    public void execute(QueueableContext context) {
        try {
            Integer invalid = 100 / 0;
        }
        catch(Exception ex) {
            sendEmail(ex);
        }
    }
}

Question 2: Can I test it like this?

@IsTest
private class MyQueue_Test {

    @IsTest
    private static void sendsEmailOnError() {

        // Exercise
        Test.startTest();
        System.enqueueJob(new MyQueue());
        Test.stopTest();

        // Verify
        System.assertEquals(1, Limits. getEmailInvocations());
    }
}
2
  • 2
    Can you elaborate "can not" please? Is that because it does not work (and what is an error), or is that because it is written somewhere not to do it or any other reason?
    – kurunve
    Apr 2, 2019 at 14:10
  • Sure, no email is "sent", not even an exception is thrown. In my logs I see a Script-Thrown Exception which is not caught by the catch(). Apr 2, 2019 at 14:23

2 Answers 2

5

The problem is that Test.stopTest executes the asynchronous code, then resets the governor limits back to the state it was in immediately before Test.startTest is called. If you really wanted to verify the output, you'd need a static method to check:

public class MyQueue implements Queueable, Database.AllowsCallouts {
    @TestVisible static Boolean emailSent = false;
    public void execute(QueueableContext context) {
        try {
            Integer invalid = 100 / 0;
        }
        catch(Exception ex) {
            sendEmail(ex);
            emailSent = true;
        }
    }
}

And from there, test it in your unit test:

@IsTest
private class MyQueue_Test {

    @IsTest
    private static void sendsEmailOnError() {

        // Exercise
        Test.startTest();
        System.enqueueJob(new MyQueue());
        Test.stopTest();

        // Verify
        System.assert(MyQueue.emailSent);
    }
}

This is one of those relatively rare conditions where the easiest way to perform a task is to inject test code to the actual code. You should do this minimally, of course, but as often as necessary.

Alternatively, a more idiomatic way to do this would be to use the Stub API. The idea here is that you'd mock out sendEmail by overriding it in the unit test. This is a bit complicated to use in practice, especially since you only need to add two lines of code.

2
  • Should I use Database Savepoints and rollbacks in the catch or does Apex care about this? I mean if I do some DML in the try... Apr 2, 2019 at 21:46
  • 1
    @RobertSösemann for testing, it doesn't matter. For production code, if you perform multiple DML and do not roll back, you may leave your data in an inconsistent state. If you're at all concerned about this possibility, definitely roll back. This is on a case by case situation, though. You'll need to analyze each case separately, and determine if partial success should be allowed.
    – sfdcfox
    Apr 2, 2019 at 22:25
3

I usually take the route of storing results in a log object as opposed to email.

If you want, you can add a trigger to the log object to send out an email. You can enable activity tracking for the emails as well which would allow you to query for the existence of an activity as opposed to checking the governor limits.

The benefits being:

  • logs are more permanent
  • logs are less likely to get lost in email
  • logs are not user specific meaning if there is an issue and the email recipient is out of office, the log can be viewed and the issue can be resolved
  • you can report on logs/activities but not email

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.