/* Framework: https://github.com/trailheadapps/apex-recipes */ @isTest @testVisible private class TriggerHandler_Test { private static final String TRIGGER_CONTEXT_ERROR = 'Trigger handler called outside of Trigger execution'; private static String lastMethodCalled; private static TriggerHandler_Test.TestHandler handler; static { handler = new TriggerHandler_Test.TestHandler(); // override its internal trigger detection handler.isTriggerExecuting = true; } /*************************************** * unit tests ***************************************/ // contexts tests /** * @description Tests the BeforeInsert method is properly brokered. */ @isTest static void testBeforeInsert() { beforeInsertMode(); handler.run(); System.Assert.areEqual( 'beforeInsert', lastMethodCalled, 'Last method should be beforeInsert' ); } @isTest static void testBeforeUpdate() { beforeUpdateMode(); handler.run(); System.Assert.areEqual( 'beforeUpdate', lastMethodCalled, 'Last method should be beforeUpdate' ); } @isTest static void testBeforeDelete() { beforeDeleteMode(); handler.run(); System.Assert.areEqual( 'beforeDelete', lastMethodCalled, 'Last method should be beforeDelete' ); } @isTest static void testAfterInsert() { afterInsertMode(); handler.run(); System.Assert.areEqual( 'afterInsert', lastMethodCalled, 'Last method should be afterInsert' ); } @isTest static void testAfterUpdate() { afterUpdateMode(); handler.run(); System.Assert.areEqual( 'afterUpdate', lastMethodCalled, 'Last method should be afterUpdate' ); } @isTest static void testAfterDelete() { afterDeleteMode(); handler.run(); System.Assert.areEqual( 'afterDelete', lastMethodCalled, 'Last method should be afterDelete' ); } @isTest static void testAfterUndelete() { afterUndeleteMode(); handler.run(); System.Assert.areEqual( 'afterUndelete', lastMethodCalled, 'Last method should be afterUndelete' ); } @isTest static void testNonTriggerContext() { try { handler.run(); System.Assert.fail('The handler ran but should have thrown'); } catch (TriggerHandler.TriggerHandlerException te) { System.Assert.areEqual( TRIGGER_CONTEXT_ERROR, te.getMessage(), 'The exception message should match' ); } catch (Exception e) { System.Assert.fail( 'The exception thrown was not expected: ' + e.getTypeName() + ': ' + e.getMessage() ); } } // test bypass api @isTest static void testBypassAPI() { afterUpdateMode(); // test a bypass and run handler TriggerHandler.bypass('TestHandler'); handler.run(); System.Assert.isNull( lastMethodCalled, 'Last method should be null when bypassed' ); System.Assert.isTrue( TriggerHandler.isBypassed('TestHandler'), 'Test handler should be bypassed' ); resetTest(); // clear that bypass and run handler TriggerHandler.clearBypass('TestHandler'); handler.run(); System.Assert.areEqual( 'afterUpdate', lastMethodCalled, 'Last method called should be afterUpdate' ); System.Assert.isFalse( TriggerHandler.isBypassed('TestHandler'), 'Test handler should be bypassed' ); resetTest(); // test a re-bypass and run handler TriggerHandler.bypass('TestHandler'); handler.run(); System.Assert.isNull( lastMethodCalled, 'Last method should be null when bypassed' ); System.Assert.isTrue( TriggerHandler.isBypassed('TestHandler'), 'Test handler should be bypassed' ); resetTest(); // clear all bypasses and run handler TriggerHandler.clearAllBypasses(); handler.run(); System.Assert.areEqual( 'afterUpdate', lastMethodCalled, 'Last method called should be afterUpdate' ); System.Assert.isFalse( TriggerHandler.isBypassed('TestHandler'), 'Test handler should be bypassed' ); resetTest(); } // instance method tests @isTest static void testLoopCount() { beforeInsertMode(); // set the max loops to 2 handler.setMaxLoopCount(2); // run the handler twice handler.run(); handler.run(); // clear the tests resetTest(); try { // try running it. This should exceed the limit. handler.run(); System.Assert.fail( 'The handler should throw on the 3rd run when maxloopcount is 3' ); } catch (TriggerHandler.TriggerHandlerException te) { // we're expecting to get here System.Assert.isNull( lastMethodCalled, 'Last method should be null' ); } catch (Exception e) { System.Assert.fail( 'The exception thrown was not expected: ' + e.getTypeName() + ': ' + e.getMessage() ); } // clear the tests resetTest(); // now clear the loop count handler.clearMaxLoopCount(); try { // re-run the handler. We shouldn't throw now. handler.run(); System.Assert.areEqual( 'beforeInsert', lastMethodCalled, 'Last method should be beforeInsert' ); } catch (TriggerHandler.TriggerHandlerException te) { System.Assert.fail( 'Running the handler after clearing the loop count should not throw' ); } catch (Exception e) { System.Assert.fail( 'The exception thrown was not expected: ' + e.getTypeName() + ': ' + e.getMessage() ); } } @isTest static void testLoopCountClass() { TriggerHandler.LoopCount lc = new TriggerHandler.LoopCount(); System.Assert.areEqual(5, lc.getMax(), 'Max should be five on init'); System.Assert.areEqual( 0, lc.getCount(), 'Count should be zero on init' ); lc.increment(); System.Assert.areEqual(1, lc.getCount(), 'Count should be 1'); System.Assert.isFalse( lc.exceeded(), 'Should not be exceeded with count of 1' ); lc.increment(); lc.increment(); lc.increment(); lc.increment(); System.Assert.areEqual(5, lc.getCount(), 'Count should be 5'); System.Assert.isFalse( lc.exceeded(), 'Should not be exceeded with count of 5' ); lc.increment(); System.Assert.areEqual(6, lc.getCount(), 'Count should be 6'); System.Assert.isTrue( lc.exceeded(), 'Should not be exceeded with count of 6' ); } // private method tests @isTest static void testGetHandlerName() { System.Assert.areEqual( 'TestHandler', handler.getHandlerName(), 'Handler name should match class name' ); } // test virtual methods @isTest static void testVirtualMethods() { TriggerHandler h = new TriggerHandler(); h.beforeInsert(); h.beforeUpdate(); h.beforeDelete(); h.afterInsert(); h.afterUpdate(); h.afterDelete(); h.afterUndelete(); System.Assert.isTrue( true, 'Expected all of these methods to have executed without throwing an exception during tests.' ); } /*************************************** * testing utilities ***************************************/ /** * @description Resets the test */ private static void resetTest() { lastMethodCalled = null; } // modes for testing private static void beforeInsertMode() { handler.setTriggerContext('before insert', true); } private static void beforeUpdateMode() { handler.setTriggerContext('before update', true); } private static void beforeDeleteMode() { handler.setTriggerContext('before delete', true); } private static void afterInsertMode() { handler.setTriggerContext('after insert', true); } private static void afterUpdateMode() { handler.setTriggerContext('after update', true); } private static void afterDeleteMode() { handler.setTriggerContext('after delete', true); } private static void afterUndeleteMode() { handler.setTriggerContext('after undelete', true); } // test implementation of the TriggerHandler @testVisible private class TestHandler extends TriggerHandler { public override void beforeInsert() { TriggerHandler_Test.lastMethodCalled = 'beforeInsert'; } public override void beforeUpdate() { TriggerHandler_Test.lastMethodCalled = 'beforeUpdate'; } public override void beforeDelete() { TriggerHandler_Test.lastMethodCalled = 'beforeDelete'; } public override void afterInsert() { TriggerHandler_Test.lastMethodCalled = 'afterInsert'; } public override void afterUpdate() { TriggerHandler_Test.lastMethodCalled = 'afterUpdate'; } public override void afterDelete() { TriggerHandler_Test.lastMethodCalled = 'afterDelete'; } public override void afterUndelete() { TriggerHandler_Test.lastMethodCalled = 'afterUndelete'; } } }