3

I have a custom business logic that will block certain profiles from editing/creating/deleting an account.

Because of other requirements, mainly around Record Types, I can't use profiles and permission sets, so I had to use triggers.

My trigger works as expected on the UI, but my unit tests fail when I catch the exception since Database.rollback(sp) didn't actually rollback my transactions when the exception is caught.

Why is the Account.Phone not not rolled back to null?

void testSomething() {

   for(Account acct : accts) {
      System.assertEquals(null, acct.Phone); => true
   }

   System.runAs(UserA) {
       System.assertEquals(true, updateAccounts(accts)); => true
   }

   for(Account acct : accts) {
      System.assertEquals(null, acct.Phone); => fails since acct.Phone = (201) 867-5309
   }
}
Boolean updateAccounts(acctsToUpdate) {
   Boolean errorThrown = false;
   Savepoint sp = Database.setSavepoint();   
   
   for (Account acct : acctsToUpdate) {
      acct.Phone = '(201) 867-5309';
   }

   try {
      update acctsToUpdate;
   } catch(Exception e) {
      errorThrown = e.getMessage().contains('Some Error');
   } finally {
      Database.rollback(sp);
   }

   return errorThrown;
}
4

A computer has two main types of data storage, called temporary memory and permanent memory (or permanent storage). Temporary memory is commonly called RAM, and it's what a computer uses when working with data. Permanent storage are hard drives, etc, and it's what a computer uses to store data for later recall.

In programming (e.g. Apex), the variables you use to insert/update/delete records are just RAM, while the finally committed records in the database are in permanent storage. The data you have in memory is only temporary until the transaction is committed to permanent storage. This is one reason why, even if you modify data in a variable, you need to query the data back to validate the values after a DML operation.

In your code, the data is rolled back, but your "copy" is now out of sync with the database. To verify that the data is correct, you now have to query the records again:

accts = [SELECT Phone FROM Account WHERE Id = :accts];
for(Account acct : accts) {
   System.assertEquals(null, acct.Phone); => fails since acct.Phone = (201) 867-5309
}

This ensures that you have the latest version of the data from the database. If you don't understand any part of this answer, try reading it a few times until it makes sense. Just remember that Apex can't work on data that's actually in the database, it has to pull data out, manipulate or observe it, and then put it back in again, just like how you need to open a Microsoft Word document and then save it when you're done editing.

3
  • Thanks for your thorough explanation @sfdcfox! What I don't fully understand is the way Apex passes arguments to functions. If I understand correctly, sObjects are passed by reference, so if I pass my accts to updateAccounts (), any updates on accts in updateAccounts () should reflect on the caller's original data, no?
    – Has Nah
    Oct 15 '20 at 5:14
  • @HasNah That's correct. The change to Phone is reflected in the unit test's variable, which is why the final assertion loop fails.
    – sfdcfox
    Oct 15 '20 at 12:31
  • @sfdxfox, your suggestion worked for the failing case! I had another test case in another isTest function that is failing. The expectation is that no errors are caught and new phone number is committed to the database. However when I read accounts from the db, phone is null, why is this happening? RAM values have been committed to db so I should be able to read them. Also those accounts are created via a testSetup method, is this related?
    – Has Nah
    Oct 15 '20 at 13:14
0

I'm not sure how your the test class is build but, you may want to explore startTest and stopTest as you are testing governor limits by updating the records

    Test.startTest();
       System.runAs(UserA) {
          System.assertEquals(true, updateAccounts(accts)); => true
       }
   for(Account acct : accts) {
      System.assertEquals(null, acct.Phone); => fails since acct.Phone = (201) 867-5309
   }
    Test.stopTest();

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_test.htm?search_text=startTest

1
  • Thanks for your suggestion, however I already have both startTest() and stopTest() wrappers and I omitted it to declutter the main concerns.
    – Has Nah
    Oct 15 '20 at 4:45

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.