5

Ok so I have a fairly complex scenario I need to test...

I have a Trigger on a MyObject__c that creates PermissionSetAssigments when MyObject__c is inserted. Because PS Assignments are system objects, they're creation must run in a future method. Because the future method can fail while the trigger succeeds, there may eventually wind up with a scenario where MyObject__c exists but the PermissionSetAssignment was not created.

To counter this, I have written a Batch Class that will be scheduled to run on intervals and create any PS Assignments that failed to create by the trigger.

Thus we arrive at my delimia... I can't properly test this functionality because of the following:

I must instate MyObject__c in the test setup. The trigger will run and a PermissionSetAssignment will be created using a future method. But this assignment doesn't get created until Test.stopTest() is called.

I then need to delete this PermissionSetAssignment so that the state will now be out of sync.

Then, I need to run my batch class and check that the assignment has been added back. However, this code ALSO won't run until Test.stopTest().

Thus, both the Batch Execute and the future PS Assignment insert are going to run in a strange sort of "Race Condition"

I'm wondering if there is some way to prevent the trigger to run in the first place. This would give me the setup I need to test.

Or if anyone has any other suggestions on how to handle testing this.

  • 1
    can you flip the problem on it`s head and try and deactivate the trigger using custom settings? i'm talking about a flag, a boolean which will sit in the trigger. – Mihai Neagoe Jan 16 '15 at 21:33
  • 1
    @MihaiNeagoe, I suppose something like that could be done... You would probably want to ensure it only happens when isTest() so you don't accidentally deactivate it when not running tests. I was hoping for a cleaner solutions... my other workaround is to pull out the batch code into a helper class. Then do my assertions just using the helper class – NSjonas Jan 16 '15 at 21:36
9

The only way to do this would be to code the functionality into your trigger. You could make use of static variables to prevent the trigger from running. Just create some class

public class StaticTest{
    public static Boolean doNotRunTrigger = false;

    public static void setDoNotRunTrigger(){
        doNotRunTrigger = true;
    }

    public static Boolean shouldRunTrigger() {
        return !doNotRunTrigger;
    }
}

And in your trigger

if(StaticTest.shouldRunTrigger()){
    //TriggerLogic
}

Then in your Test Class, just call the StaticTest.setDoNotRunTrigger() at the beginning of the test method and the trigger won't run!

  • Well... it still feels a bit hacky (but what doesn't in salesforce) but I like it better than the alternatives. – NSjonas Jan 16 '15 at 22:43
  • 2
    We use a custom setting-backed version of this code-- we can turn off triggers in production in real-time if we discover an event that requires a patch. We also use the same logic in our test methods to reduce SOQL calls and deployment time. In our case, it takes ~10 seconds to save an account, of which we need an account to cover ~150 tests, while turning off triggers reduces the save to about ~1 second, a net savings of ~1490 seconds (do the math on this...) across over all our test methods. – sfdcfox Jan 16 '15 at 22:56
3

I really liked @Bradley's approach however found it more logical to place the overwrite on the trigger instead.

In your trigger you would do something like below:

public with sharing class ApprovalTriggerHandler {
    public static Boolean bBypassTrigger = false; // set to `false` to skip  trigger code 
                                                  // execution, ex. during unit testing
    ...
    public static void onBeforeInsert(List<cms__Approval__c> oApprovalList) {   
      if (ApprovalTriggerHandler.bBypassTrigger) { return; }
      ...
    }
    ...
}

And in your test class you would disable running of triggers as follows:

private class ApprovalSchedulabe_Test {

    static testMethod void smokeTest() {
        ApprovalTriggerHandler.bBypassTrigger = true; // disable running of trigger code
        ....
    }
    ...
}
  • That's what my typical pattern looks like as well, though I would rename the Boolean something more informative like bypassTrigger. Then I would just do if (bypassTrigger) return; (no need to compare your flag to another Boolean). – Adrian Larson Oct 23 '17 at 18:33
  • Curious why you think prepending by b makes the variable name more readable? – Adrian Larson Oct 23 '17 at 18:55
  • @AdrianLarson it's a personal convention where I prefix with primitive type to reduce mental friction when reading code months later - danielsokolowski.blogspot.ca/2016/04/… – Daniel Sokolowski Oct 24 '17 at 13:58
0

I hope this is not the only way, but if it is, then you should definitely write your test in this syntax.

try {
    ApprovalTriggerHandler.bBypassTrigger = true;
    System.debug('Statement after insert.');
    // Insert other records
}
catch (Exception e) {
    ApprovalTriggerHandler.bBypassTrigger = false;
    // Handle this exception here
}

Otherwise your system stays in an unstable status, if something went wrong.

I prefer that you use the Test.isRunningTest() -> https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_test.htm

So mostly i write my code that the trigger only includes the before and after logic, because i like to test the logic of the Trigger. Potentially a trigger can look like this:

trigger onContract on Contract (before insert) {
    if(!Test.isRunningTest()) {
        Contract_Handler handler = new Contract_Handler(Trigger.new);
        handler.onBeforeInsert();
    }
}

So i prevent the executing of the trigger in the test always. This means that i only have unit tests, but this looks for me more save then set a variable.

  • -1 from me. This pattern makes it literally impossible to get coverage on your trigger for any lines which should actually run. You should not touch your trigger body at all for use of this pattern. If you want to default the trigger to off, have your handler set bypassTrigger = Test.isRunningTest(). That way, you can still toggle it on/off when you run certain tests in your suite. – Adrian Larson Oct 8 at 15:32
  • The trigger doesn't contain any logic except call the handler and the handler you can test. The problem with the bybass i see is that accidentally something went wrong in the test the bypass will not reset, right? – Garllon Oct 9 at 16:13
  • If you are hitting a failure path in your test where your bypass flag isn't getting reset, it fails either way. And while your number of lines of code is low in this trigger pattern, you still have lines in your trigger you cannot cover, and if you have every trigger event, that might mean your maximum coverage in this pattern is roughly 10%. – Adrian Larson Oct 9 at 16:19

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.