4

I tend to create unit tests more than integration tests in Apex.

When creating unit tests, one step is to prepare data (insert/update). In this step, shall I

    1. disable trigger logics (assuming a trigger framework that allows stopping triggers is used), then prepare data?
    1. or keep trigger enabled and consider it as part of the platform.

With #1, I don't need to understand the current trigger logics, data is prepared without interference from the triggers.

I see #2 is seen frequently when I read code of others.

Any good tips?

4 Answers 4

4

The upside to option #1 is that it can significantly reduce deployment times. In one organization I worked at, I was able to reduce the deployment times from 45 minutes to 15 minutes simply by disabling triggers during @testSetup methods and other test initialization methods (most tests started off with @isTest static void test() { initialize(); ... }). Aside from that, it allows to see which unit tests are actually testing your triggers via a simple code coverage report. You also reduce the risk of artificial governor limit failures that would not be a problem in production.

The downside to this technique is that you need to make sure you accurately model the data as if the triggers had executed. This is usually accomplished by way of a Test Data Factory class. Failure to set the data appropriately can hide logic bugs.

The upside to option #2 is that will more accurately generate data as if it were created "for real". This reduces the amount of time spent maintaining the Test Data Factory class, and can help highlight problems with changes to the system that invalidate some base assumption about the data model.

As you might imagine, the downsides to option #2 are the the opposite upsides for option #1. Namely, you'll have a bunch of unit test code coverage for tests that were not actually testing those triggers, and worse, if you forget a unit test class for a trigger, you'll never really know, because the test coverage will show they were covered (oops!). Also, deployment times can be significantly longer if you do this. Finally, you won't get "real" governor limit usage with this technique, and your tests may fail artificially, even though the code will never hit those limits in production.

I personally prefer option #1, as it makes deployments faster and more accurate, and the associated risks are minimal with some discipline. You have to consider how frequent your deployments are and the risks associated with either option. The only advice I can give is that you stick to one or the other for consistency.

4

In general, if you have a framework which allows you to bypass triggers for test data setup, you should do it.

My primary argument for doing so is that Unit Tests should test individual units. These tests should be atomic. If you are testing how some AccountService functionality works, that test should not be dependent in any way on what logic is in your Account trigger. You can include some flow tests which cover interactions between the two, but that type of testing should be the exception, not the rule.

My secondary argument for doing so is that running the triggers against every object you set up data for in you tests consumes a vast amount of unnecessary CPU (and clock) time during your deployment cycle. As orgs grow more complex, every step you take to keep deployments trim will save you much stress, and help you investigate and resolve any outages more quickly.

3

In my experience, it is always best to consider the full end to end flow which includes triggers.

The biggest advantage of this approach is that you always run the "worst case" scenario with regards to all types of limits that you can hit and are not hit by surprises when a user suddenly gets a 101 limit error because he/she was not bypassing.

This also helps in the long run as you will start noticing limits/boundaries earlier.

3

This is where the Enterprise Patterns and Apex Mocking comes in:

Enterprise Patterns

  • Selector layer (queries)
  • Services layer (business requirements)
  • Domain layer (trigger handler/methods on the sobject)
  • Unit of Work layer (transaction)

Apex Mocks

  • allows mocking of these layers

If you architect your code this way, then unit tests can be written on any layer by

  • mocking the results from queries as SObjects (no setup DML required)
  • mocking the unit of work layer so no actual DML is executed but all arguments to DML can be verified
  • mocking service layer to verify called or to mock results/exceptions

The end-end testing is still required in order to verify that no Validation Errors occur, any process Builders/Flows execute, and to test Limits, among other reasons. But a vast majority of unit testing can be done without ever doing any DML setup or DML execution.

The best resource for explaining all this is Lightning Platform Enterprise Architecture by Andrew Fawcett, 2nd or 3rd Editions. There are also trailheads on Enterprise Patterns.

You must log in to answer this question.

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