0

I am sure this has been asked before but what we are seeing is unit tests failing due to a process builder change or a new PB being added . We either see flow execution error we run out of CPU time based on if creating an account or closing an opportunity multiple process builders run. Some times they fail because the PB is expecting certain data and unit test needs to create it .Most of the time we see tests becoming more of integration tests than unit tests. My question is what is the best practice to be followed by both PBs and Unit tests? We are trying to bypass process builders by setting flags during unit test runs , is there a better way to handle this?

1
  • 3
    If you can avoid actually hitting the database in most of your unit tests, the better. But for integration tests you would want to see how the entire process works out end-to-end. It sounds to me like some performance optimizations need to be made to get around this issue. Keep in mind, even if you do set a field on a record to bypass a process builder, the process builder still starts and runs to the point at which it finds out it needs to do nothing before stopping which consumes CPU. Your best option is to find ways of consolidating PBs or migrate to some other process automation.
    – nbrown
    Dec 16, 2020 at 15:50

2 Answers 2

0

The best pattern I've seen for doing true unit tests where no DML is executed is the Unit of Work pattern as evidenced in this Trailhead combined with ApexMocks

A trivial example - a class/method that inserts an Account - based on the fflib library

public MyClass {
  public void myMethod() {
    fflib_ISobjectUnitWork uow = Application.UnitOfWork.newInstance();
    uow.registerNew(new Account(Name = 'Foo', Website = 'www.foo.com'));
    uow.commitWork();
  }
}

The testmethod

@Istest
private class MyTestClass {
  @IsTest
  static void givenXXWhenYYThenVerifyAccountCreated() {
    // Given mocking framework setup
    fflib_ApexMocks mocks = new fflib_ApexMocks();
    // Given mock UnitOrWork
    fflib_SObjectUnitOfWork mockUow = (fflib_SObjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class);
    Application.UnitOfWork.setMock(mockUow); 

    // when class/method under test invoked
    new MyClass().myMethod();

    // then verify proper Account object created (magic of apexMocks here)
    ((fflib_SObjectUnitOfWork)mocks.verify(mockUow,mocks.times(1)
                                   .description('Account sb created')))
                .registerNew(fflib_Match.sObjectWith(new Map<SObjectField,Object> {
                     Account.Name => 'Foo',
                     Account.Website => 'www.foo.com'
                })); 


    ((fflib_SObjectUnitOfWork)mocks.verify(mockUow,mocks.times(1)
                                   .description('commitwork sb called')))
                .commitWork();
   }
}

The lesson here is that you are unit testing that the Account SObject was properly constructed and by mocking the UnitOfWork class, the actual DML (insert) of the Account never occurs.

The Unit Of Work pattern is meant to be used in conjunction with the Domain, Service, and Selector patterns - each of which can also be mocked in testmethods

Of course, mocking DML in testmethods only serves to do unit tests and not integration tests as Validation Rules won't be executed (as you aren't doing DML) nor will you detect CPU time issues or too many SOQL issues (as you will most likely mock your selectors to avoid having to DML any testdata)

The definitive reference on Enterprise patterns and mocking is Salesforce Lightning Enterprise Architecture

0

The best scenario is, in my opinion, to revisit your declarative processes when they break your unit tests.

Most of the time your unit test needs a little more data to make the declarative process to work, or you can change the process and make it not work when there is missing data.

For example:

You have a process that checks when an account has a certain record type. If it has that record type, the process should do something about it or a related record.

Sounds pretty straightforward. But what happens if the record type is not assigned to the account and the process is fired? Is it the unit test's fault that you don't want or need to explicitly specify the record type? Of course not. The process should be set up in a way that it checks if the record being handled contains a record type before trying to access it.

Given a scenario like this where you can actually modify the declarative process, I don't think mocking is necessary.

If you are hitting CPU limits only during tests you might also consider to check them for the Test.startTest and Test.stopTest methods, which "increase limits" during test execution by creating a new execution context.

You must log in to answer this question.

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