Showing posts with label unit tests. Show all posts
Showing posts with label unit tests. Show all posts

Monday, 19 January 2026

Run Relevant Tests Annotations in Spring '26

Image created by ChatGPT5.2 based on a prompt by Bob Buzzard

Introduction


In my first post on this new functionality I did a bit of digging into which tests are chosen when code is changed. If leaving it up to Salesforce doesn't quite cut it, there's the option to influence things via the two new parameters for the @isTest annotation. 

As in the first post, I've got a small set of classes with dependencies that are sometimes static and sometimes dynamic. As a refresher the key classes are:
  • OpportunityUtils
    The protagonist in my little drama is a class named  This implements an interface (OpportunityUtilsIF) with a single method, getBigDeals(), which receives a collection of opportunities and returns a new collection containing just those opportunities with a value greater than or equal to 250,000.

    There is a dedicated test class (OpportunityUtilsTest) which directly instantiates the class and executes a zero/one test. 
  • OpportunityEOD
    This contains a single method (EODProcessing) that extracts all opportunities created today, creates an instance of OpportunityUtils, extracts just the big deals, appends ' - BIG DEAL' to their name and updates them.

    There is a dedicated test class (OpportunityEODTest) that inserts test opportunities of varying values, instantiates OpportunityEOD and executes the EODProcessing method, then extracts all opportunities from the database that are greater than or equal to 250,000 and asserts each name contains ' - BIG DEAL'.

  • OpportunityWrapLevel1
    This contains a single method (EODProcessingWrapLevel1) that instantiates the OpportunityEOD class and executes the EODProcessing method.

    There is a dedicated test class (OpportunityWrapLevel1Test) that inserts test opportunities of varying levels, instantiates OpportunityEODWrapLevel1, executes the EODProcessingWrapLevel1 method, then extracts all opportunities from the database that are greater than or equal to 250,000 and asserts each name contains ' - BIG DEAL'.

  • OpportunityEODInjection
    This  contains a replica of the EODProcessing() method, but rather than directly instantiating OpportunityUtils it is passed a parameter (implementing the OpportunityUtilsIF interface. There is a dedicated test class (OpportunityEODInjectionTest) that delegates to a test factory to dynamically create an instance of OpportunityUtils based on the class name - at no point is OpportunityUtils directly referred to. The test mirrors the other EOD tests, inserting opportunities, carrying out the EOD processing and verifying that ' - BIG DEAL' is appended where appropriate.

As we are now in the scratch org preview window, I was able to use my pre-release developer edition as a Dev Hub and create a Spring '26 scratch org, which speeds things up enormously and lets me scrap everything and start again from scratch with minimum effort.

@IsTest(critical=true)


This parameter tells the Salesforce test engine that the tests in this class must execute when a deployment takes place. I originally misread the docs on this and thought the test only executed if the payload contained Apex changes, but that isn't the case. Setting a test class as critical means it always executes for a deployment that runs relevant tests regardless of what is being deployed. To confirm this, I returned to the classic example of a configuration change that breaks tests with ease - the validation rule!

I marked my OpportunityEODTest class as critical, attempted to deploy an Opportunity validation rule that required one of Description or Lead Source to be populated, and duly watched the test execute and the deployment fail! 

This is a great addition, as the critical tests are identified without the deployer having to remember which tests they should always run. That said the deployer does still have to remember to deploy with the RunRelevantTests option, so it's not a silver bullet.

@IsTest(testFor='<classes_and_triggers>')


This parameter comes in handy if the Salesforce test engine isn't picking up all the tests that matter. In my first post I explained how it isn't really reasonable to expect the test engine to pick up dynamic instantiation of the OpportunityUtils class based on its name, which is how it's used in OpportunityEODInjection. Using the testFor parameter I can give the test engine the helping hand it needs:
    @isTest(testFor='ApexClass:OpportunityUtils')
    private class OpportunityEODInjectionTest {
        @isTest
        static void TestEODProcessing() {
           ...
        }
    }
  
Note that I don't have to include OpportunityEODInjection in the list of classes identified in the testFor parameter. I'm adding to the tests that are executed, not overriding them. This test is also executed if I change the OpportunityEODInjection class, as it has a direct dependency on it which the Salesforce test engine can pick up.

Even though that is the case, I think in a real-world environment I'd prefer to list all the classes/triggers that the test class is associated with, as it improves clarity and saves a developer having to figure things out manually, even if there is overhead to create and maintain this information. 

More Information


Follow on LinkedIn



Saturday, 5 November 2022

System.Assert Class in Winter 23

System.Assert

The Winter 23 release of Salesforce introduces a new Apex class in the System namespace - the Assert class. This contains methods to assert (or check) that the results of the code under test are as expected. 

We already have a collection of assert methods in the System class itself, so why do we need more? The surprising answer is that we don't! The existing assert methods can be purposed to confirm any condition you care to think of:

  • assert - confirm that a parameter evaluates to true
  • assertEquals - confirm two parameters evaluate to the same value
  • assertNotEquals - confirm that two parameters do not evaluate to the same value
In fact you could argue that we don't need all the methods that we have - the assert method alone with an appropriately constructed expression can confirm any behaviour.

The reason we have the new System.Assert class is the same as the reason that we were originally given the assertEquals and assertNotEquals - to provide clarity around the intent of our code. If we are interested in testing the equality of two variables then it's much easier to understand the intent using:
   System.assertEquals(firstValue, secondValue);
than
   System.assert(firstValue==secondValue);
The System.Assert class provides a number of new methods that allow you to write clearer unit tests, helping those that come after you get to grips with your code quicker

areEqual, areNotEqual 


These mirror the functionality of the existing System.assertEquals/assertNotEquals, but with more clarity - your code is verifying that the two parameters are equal or are not equal to each other.

isTrue/isFalse


Verify that the parameter evaluates to false or true. You could achieve the same thing using the existing methods, for example to check that the found variable is false:
          System.assert(!found);
          System.assertEquals(found, false);
          System.assertNotEquals(found, true);
      
but in each of these you hafe to look at the expressions that are used to generate the parameters, whereas with:
          System.Assert.isFalse(!found);
it's obvious what I'm trying to do

isNull/isNotNull


Verify the parameter passed is null or isn't null - this is more powerful than it might appear at first glance. Consider the following assertion:
          System.assertEquals(searchResults, 'null');
This looks reasonable, but only useful if you want to confirm that searchResults is a String with the 
contents 'null', rather than a null value. Sadly there's no way for me to determine which one the original author intended - instead I have to examine the code under test and figure out what the result should be Compare (see what I did there!) this with:
          System.Assert.isNull(searchResults);
and there's no room for doubt.

isInstanceOfType/isNotInstanceOfType


A slightly less obvious method, but one that you'll find very useful if you regularly find your unit tests catching exceptions and checking the correct one was thrown, handling collections of generic sObjects, or, like me, you've written a few classes that parse field history tracking tables and turn the old/new values back into their original data types.

Using the exception as an example, there's a few ways you can verify this with the old methods:
  • Only catch the specific type of exception you are expecting and let anything else cause the test to fail - not the greatest experience.
  • Catch the Exception superclass and use the instanceof operator to determine the actual type:
         System.assert(caughtException instanceof DMLException);
                
  • Catch the Exception superclass and use the getTypeName method to determine the actual type
         System.assertEquals(caughtException.getTypeName(), 'System.NullPointerException');
or use the new Assert class and make it very clear you are interested in the type of the parameter using:
          System.Assert.isInstanceOfType(caughtException, DMLException.class);

fail


Another method I'm particularly pleased to see. Often I'll be testing some code that should throw an exception, but I need a way to mark the test as a failure if it doesn't:

       try {
              // execute method
              System.assert(false, 'Should have thrown exception');
       }
       catch (Exception exc) {
           // expected behaviour - nothing to do
       }
    
To the casual browser, this looks like I'm verifying some behaviour after the method executes, and swallowing any exceptions that might be thrown - not a great test at all. The fail method gives me a mechanism to clearly indicate that if the code doesn't throw an exception then something is awry:
       try {
              // execute method
              System.Assert.fail('Should have thrown exception');
       }
       catch (Exception exc) {
           // expected behaviour - nothing to do
       }
    

Always Use Assert Messages


I'm guessing that we might have a few readers who are relatively new to Apex testing - the best piece of advice I can give you is to always use the variant of an assert method that takes a message parameter, and make that message useful. 

<comic-aside>
There's an old joke about a pilot flying a passenger in a small plane around a Seattle who experiences a navigation and comms outage . The pilot heads for a tall building with lit up offices, while the passenger writes "Where am I?" on a piece of paper and holds it up for the occupants to see. One grabs a piece of paper, writes on it and holds up the message "You are in a plane". The pilot immediately sets a course and lands safely a couple of minutes later. The passenger asks how the message made a difference, and the pilot replies "The information was 100% accurate and no help at all, so I knew that was the Microsoft support building".

</comic-aside> 


Consider the following test :
      Integer pos=2;
      System.Assert.areEqual(3, pos);
Upon running this, you'll get the following output:

System.AssertException: Assertion Failed: Expected: 3, Actual: 2

Much like 'You are in a plane', this is 100% accurate and no help at all to someone who isn't intimately familiar with the codebase. The message parameter gives you an opportunity to provide some accurate and helpful information, for example:
    Integer pos=2;
       System.Assert.areEqual(3, pos, 
                        'The matching record should be found at position 3 of the list');
  
Which gives the output:

System.AssertException: Assertion Failed: The matching record should be found at position 3 of the list: Expected: 3, Actual: 2
One final word of advice - always remember the message is describing the error, not the successful outcome. You'd be surprised how many times I've seen something like the following:
System.AssertException: Assertion Failed: The matching record is at position 3 of the list: Expected: 3, Actual: 2
when it really isn't, that would be the case if the test passed!

Related Information




Saturday, 3 July 2021

Low Code Still Needs Unit Tests

Introduction

First off let me make clear this is not dumping on low code or some King Canute like attempt to push back the advance of clicks not code in favour of having to write every tiny automation in Apex. When I started working with Salesforce back in 2008, I didn't look at workflow rules and think "Oh nose, I wanted to write a bunch of code to update the contents of a rich text area field", I thought 'That's cool, someone else can configure the simple automation and I get to work on the more challenging scenarios". 

On the developer side we've spent years convincing people that unit tests are a good thing, and we even have test driven development that turns the requirements into tests as a first step, after which we write the code that allows the tests to pass. Some people still moan about having to write tests to deploy to production, but most of us recognise this is a good thing and we'll be grateful we had to do it at some point in the future. 

So when I see messaging (from Salesforce and many others) that not being able to write unit tests is a benefit of whatever Low Code platform is being talked about, it strikes me as an attempt to brand a negative as a positive without explaining the downsides. You might be able to get things into production quicker, but realistically you should be swapping unit test effort for extended QA and UAT effort, not just trousering the difference. If you don't, it's quite likely you are getting bugs into production quicker!

Unit Testing is Important

Unit testing is basically writing code that verifies that other code works as expected. Now that low code includes more and more business logic, and the cyclomatic complexity is increasing, unit testing is something which is needed more than ever. As an aside, I felt the same when Aura components were first introduced and moved a bunch of business logic to the front end, so I did a talk at Dreamforce on unit testing those.  

Unit testing brings the following benefits, which apply equally to all types of code:

  • The nearer you find a bug to the developer, the less it costs to rectify.
  • With good test coverage and appropriate assertions, you can make changes with confidence that if you break behaviour that someone else is relying on, one or more tests will fail (this is the main benefit for me).
  • If someone else makes a change that breaks your code (adding a validation rule is the classic situation here), the next time the unit tests run this will be picked up. 
  • It keeps your users happier, as more bugs are found during the development cycle rather than after the system has been released to them.

Testing No and Low Code

No Code tends to be discrete units that can be used to apply automation, like a workflow rule that sent an email and updated some fields if a record transitioned to a specific state, or a validation rule that checks dependencies between fields. There's no point in unit testing that with many different combinations of record fields, for the same reason that in Apex we don't write unit tests to check that the compiler and run time is working as expected. The workflow engine is tested by Salesforce and we can trust it. Manually running a few records through is fine for testing a scenario like this, as the logic that causes the workflow or validation rule to fire is typically very small and rarely changes.

In Low Code, a number of these kind of items are assembled into some more complex automation in a flow, including conditional logic and loops, which is much closer to traditional code. The medium of expression might be different, but we are now in a world of complexity and reuse, especially if it's a subflow that can be dropped into any other flow. In this world it makes sense to run the flow with a variety of inputs, with the underlying database in a variety of states, and as users with a variety of profiles, and many different combinations if these. Doing all this manually every time there is a change isn't going to be practical. Doing it via test automation tools is possible, but tends to be testing an entire transaction rather than the smallest unit, and it can't happen in production as the changes aren't rolled back at the end of the test.

Right now the only way to do this is in a genuine test environment is to write Apex tests. This somewhat defeats a key benefit of flow for smaller organisations - they would still need a developer on staff to write the unit tests for the flows that the admins have created. (Slightly off topic, I also think it would be hard to find a developer who wanted that job for any length of time, so you'd have to be prepared for some significant churn). Apex can't hook directly into the flow engine in all cases though, so for record triggered there's a compromise that the transaction must actually complete, after which verification that the flow aspect had the desired effect can take place. This is more like an integration test, as there are other aspects of automation taking place that could impact the work done by the flow, but it's definitely better than nothing. Once you have Apex tests, you can take advantage of many existing solutions for scheduled execution, making them part of a deployment pipeline etc.

What Might the Future Hold

Ideally what we need is a Low Code test builder that can execute flows/subflows in isolation and generates actual unit tests, with assertions, that run in an isolated environment where the transactions are rolled back open completion. Generating Apex seems like a reasonable approach to me, as does targeting another engine. Given all the effort that has gone into flow over the last few years, it feels like this should be doable. 

Many years ago when Salesforce was seen as shadow IT, my approach to bring the technology departments into the fold was to tell them how their governance and best practice was needed to ensure that things were done properly and were able to scale. Bringing best practice from the Pro Code world feels like a good way to get everyone on board with the Low Code approach.