2

The question might be a little vague. I'll try to give more context!

Basically, I have a trigger that in one scenario, a variable (sentEmail) is set to true, and in others, it's set to false.

In my test, I want to confirm that after one test scenario, the variable was set to true (thus, the test actually tests that an email was sent in one case), and in the other case, I want to confirm that it's still false (no email should be sent).

So I have a public boolean returnStatus() method that returns this variable...but I don't know how to retrieve this. For example, if my trigger were a class, I could simply reference in the test class, myClass.theMethod()

This doesn't seem to be the same case with boolean someVal = myTrigger.theMethod();

Knowing what I'm trying to accomplish, how can I go about this?

I don't want my test to just have code coverage; I want it to actually represent scenarios and validate the outcomes. Thoughts?

trigger updateAlert on customObject__c(after update){

    boolean sentEmail = False;
    for(CustomObject__c o : trigger.new){
        // other code happening here...
        // . . . fast forward to . . .
        sentEmail = True;
    }

 public boolean returnStatus(){
      return sentEmail;
 }

 }

I included that last method thinking I could call it from my test to retrieve that variable. How else can I grab it?

2
  • 1
    Why are you not using TriggerHandler pattern? its more useful in such cheeky situations Apr 13, 2018 at 17:30
  • 1
    It would be helpful if you provided a code example of how you're using your variables in your trigger. Depending on how they're used, you could call them from a helper class that's public which would give you the context to test them in your unit test.
    – crmprogdev
    Apr 13, 2018 at 17:32

1 Answer 1

3

Move your code to an Apex Class and you will be able to test it.

You should read up on Trigger Best Practice (emphasis mine):

Logic-less Triggers

Another widely-recognized best practice is to make your Triggers logic-less. That means, the role of the Trigger is just to delegate the logic responsibilities to some other handler class. There are many reasons to do this. For one, testing a Trigger is difficult if all of the application logic is in the trigger itself. If you write methods in your Triggers, those can’t be exposed for test purposes. You also can’t expose logic to be re-used anywhere else in your org. Good old OO principles tell us that this is a bad practice. And to top it all off, cramming all of your logic into a Trigger is going to make for a mess one day. To remedy this scenario, just create a handler class and let your Trigger delegate to it.


For your specific example, that would look something like:

Trigger

public trigger MyObject on MyObject__c (after update)
{
    if (trigger.isAfter && trigger.isUpdate)
    {
        MyObjectService.doStuff(trigger.new);
    }
}

Service

public with sharing class MyObjectService
{
    @TestVisible static Boolean sentEmail = false;
    public static void doStuff(List<MyObject__c> recordS)
    {
        // do some stuff
        sentEmail = true;
    }
}

However, please note that you can use the Limits class to determine if an email was sent. You should move your logic to a class anyway, but don't need a static flag to track if you sent emails. For instance, if your doStuff method calls Messaging.sendEmail(...), you can use the following assertion in your test:

List<MyObject__c> records = new List<MyObject__c>();
// populate

Test.startTest();
    MyObjectService.doStuff(records);
    Integer emailInvocations = Limits.getEmailInvocations();
Test.stopTest();

system.assertEquals(1, emailInvocations, 'An email should be sent');

One more note. It is common to filter on values from your oldMap. I recommend you do all such filtering in a separate method, or use a library such as Selector. The basic idea is just:

public with sharing class MyObjectService
{
    // start this method with a truthy verb
    // e.g. hasOwnerChanged
    public List<MyObject__c> applySomeFilter(List<MyObject__c> newRecords, Map<Id, MyObject__c> oldMap)
    {
        List<MyObject__c> output = new List<MyObject__c>();
        for (MyObject__c record : newRecords)
        {
            // comparison
        }
        return output;
    }
}

Then you just pass the return value to your other method.

MyObjectService.doStuff(
    MyObjectService.applySomeFilter(trigger.new, trigger.oldMap)
);
6
  • In the document you provided, there is nothing I can see in the actual trigger example that calls the other class...can I get a little guidance? I'm assuming I call the class and pass the trigger.new as a param..? Apr 13, 2018 at 17:42
  • Basically, yeah. Heres an example, and another The details of the implementation are up to you (for example, my handlers inherit an abstract class to reduce boilerplate of accessing trigger variables, instead of passing the context along) Apr 13, 2018 at 17:44
  • thanks!! so looping through trigger.new happens in the helper? Apr 13, 2018 at 18:03
  • 1
    @NataliePaige Yes, never iterate your collection within the trigger. You need to follow a bulk paradigm, and calling any method which consumes governors (of which email invocations is one) will explode if you call it within a loop.
    – Adrian Larson
    Apr 13, 2018 at 18:04
  • 1
    @NataliePaige Added guidance around filters as well. I actually presented basically this exact topic at Snowforce. Here are my slides.
    – Adrian Larson
    Apr 13, 2018 at 18:20

You must log in to answer this question.

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