2

I am writing a test for a 'before insert' trigger. In the trigger, an exception is thrown if there is a problem with the validation (by design). In production, the trigger catches the exception and sends an email, but during testing, the exception is rethrown so it can be caught by the test. I did it in this way for a few reasons:

  1. Data could be propagated to the test
  2. It is not desirable to send email when testing
  3. A custom exception is easier to test for

The trigger code is similar to:

trigger ValidationTrigger on ValidationObject (before insert)
{
    try { ValidationTriggerHandler.throwOnErrors(trigger.new); }
    catch (ValidationException xception)
    {
        EmailUtil emails = new EmailUtil()
        //prepare email message(s) with 'emails' object here

        if (EmailUtil.DoSend) { emails.send(); }
        else 
        { 
            if (Test.isRunningTest()) 
            { 
                //custom exception re-thrown so test class can process it
                throw xception; 
            } 
        }
    }
}

And the test class is similar to this:

static testMethod void DuplicateContact()
{ 
    EmailUtil.DoSend = false;

    //insert test data to cause the validation to fail

    try
    {
        ValidationObject object = new ValidationObject();
        //populate object

        //this insert will cause the trigger to execute
        insert object;

        system.assert(false, 'An expected exception was not thrown');
    }
    catch (ValidationException xception)
    {
        //never executed
    }
    catch (DmlException xception)
    {
        system.debug('CAUGHT DML EXCEPTION ' + xception.getMessage());
        {
            Exception innerException = xception.getCause();
            if (innerException != null) 
            { 
                //this line is not printed in the debug log
                system.debug('InnerException message: ' + innerException.getMessage()); 
            }
            else
            { 
                //this line is printed in the debug log
                system.debug('no inner Exception'); 
            }
        }
    }      
}

The problem I have is this: The custom exception that is re-thrown in the trigger causes a DML exception because the insert was unsuccessful. This in turn means the custom exception has been handled and can't be caught by the test. I called the 'getCause' method of the DMLException which is supposed to return 'the cause of the exception as an exception object', but it is null.

What is really frustrating about this is that the debug log actually says 'caused by: ValidationException: Script-thrown exception' (this is not printed from my code). So Salesforce actually states it knows the exception that caused the problem, but it isn't giving me access to it via the 'getCause' method of the exception that Salesforce throws.

So, how can I get access to the custom exception in the test? Is there a better way to test that a trigger handles errors properly?

  • Might be helpful to know what is in 'ValidationTriggerHandler'. – dphil May 27 '15 at 18:52
  • Why not just set a Public Boolean flag to false at the beginning of your test method and have the trigger set it to true in the if (Test.isRunningTest()) clause? Use a custom setting or a utility class for the purpose to handle it. – crmprogdev May 27 '15 at 19:02
  • The 'ValidationTriggerHandler' throws a custom exception if the validation fails, and this is the exception I'd like access to in the test. – acdupont May 27 '15 at 19:23
  • I don't want to just set a flag in the Test.isRunningTest because I want to actually examine what data is in the custom exception so I can be sure the validation was handled properly. – acdupont May 27 '15 at 19:23
  • If I can't get access to the custom exception using the Salesforce framework, I guess I could do something like create a TestUtility class that contains a static map, push the exception or data to the TestUtility in the Trigger, and access the TestUtility from the test if an exception is caught in the test. I suppose this would be similar to setting a global flag. – acdupont May 27 '15 at 19:34
3

Do not mingle "test code" with real code when you can help it. That only muddies the waters. Instead, you should just send the email.

Within a unit test, all changes to the database are rolled back at the end of the test. This includes outbound messages, sending emails, and so on. Calling Messaging.sendEmail doesn't actually send an email, it places it into a queue for sending. It won't be sent until the transaction fully commits to the database, which it will not, since it is a unit test.

Your limits will be used up like normal, but nothing will actually be sent to anybody, since this would violate the unit tests cause no permanent change rule of unit testing.

From there, you can check to see if the email was spooled for sending:

System.assertEquals(1, Limits.getEmailInvocations(), 'Expected an email to be sent.');

There's no need to mess with custom exceptions, etc, for this purpose, and you can still probably get your code coverage up quicker without resorting to exceptions.

  • I have read many web pages about sending email, and this is the first mention that an email actually can't be sent in testing because the transaction is rolled back at the end of the test. Everything I read says that email is turned off in a sandbox so you don't accidentally send email to customers. I want to be sure I understand, you are saying I can turn email on in the sandbox and calls to Messaging.sendEmail(emails) will only be added to the queue and not sent because the transaction is rolled back. Is this correct? – acdupont May 28 '15 at 14:31
  • 2
    @acdupont Certainly. Absolutely no side effects, except debug logs, are generated as a result of running a test (those logs you review after the test are done, of course). Those emails will never get sent, so you can use them just as you would in live code without worrying about emailing anybody anything accidentally. Feel free to try a test method whose only purpose is to email yourself, and you'll see that it's true. However, keep in mind that you will send emails to people from sandbox if you're not running a test (e.g. if you manually send an email). – sfdcfox May 28 '15 at 17:45
  • I accepted this answer because when testing the trigger, I should be testing the functionality that is used in production, which would be sending an email. I can test the exception by testing the helper class. But, I would still like access to the Messaging.Email object so I can verify a few fields and be sure the right message is sent for the invalid state that was discovered. Can you access the email queue (a brief search didn't turn up anything)? – acdupont May 28 '15 at 18:24
  • @acdupont That you can't do. For this, you might include a static variable so you can inspect the results afterwards, but I would think that this is ordinarily overkill, and violates the separation of unit testing and live code. Instead, I would split this into two tests. First, write a utility method for constructing the email (e.g. it returns a Messaging.SingleEmailMessage). This you can inspect as a single test. Then, call that utility method from your trigger, and write a unit test for the trigger. This keeps the features decoupled and independently monitored. – sfdcfox May 28 '15 at 20:37

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.