Sunday 20 November 2022

LWC Alerts in Winter 23


Introduction

The Winter 23 release of Salesforce provided something that, in my view, we've been desperately seeking since the original (Aura) Lightning Components broke cover in 2014 - modal alerts provided by the platform. I'd imagine there are hundreds if not thousands of modal implementations out there, mostly based on the Lightning Design System styling, and all being maintained separately. Some in Aura, some in LWC, but all duplicating effort.

I feel like we have cross-origin alert blocking in Chrome to thank for this - if that wasn't breaking things then I can't see Salesforce would suddenly have prioritised it after all these years - but it doesn't matter how we got them, we have them!

Show Me The Code!

The alerts are refreshingly simple to use too - simply import LightningAlert:

import LightningAlert from 'lightning/alert';

and then execute the LightningAlert.open() function:

    async showAlert() {
        await LightningAlert.open({
            message: 'Here is the alert that will be shown to the user',
            theme: 'warning',
            label: 'Alerted',
            variant: 'header'
        });
    }

and the user sees the alert


The LightningAlert.open() function returns a promise that is resolved when the alert is closed. Note that I've used an async function and the await keyword - I don't have any further processing to carry out while the alert is open, so I use await to stop my function until the user closes the alert. 

Demo Site


When there's a component like this with a number of themes and variants, I typically like to create myself a demo page so I can easily try them all out when I need to. In this case I have a simple form that allows the user to choose the theme and variant, then displays the alert with the selected configuration. 



In the past I'd have exposed this through one of my Free Force sites, but those all disappeared a few months ago so I needed to start again. The new location is https://demo.bobbuzzard.org, which is a Google Site with a custom domain. This particular demo can be found at: https://demo.bobbuzzard.org/lwc/alerts  - it's a Lightning Web Component inside a Visualforce Page using Lightning Out, so with the various layers involved it may take a couple of seconds to render the first time. It does allow guest access though, so worth the trade off in my view. 

Related Posts


Saturday 12 November 2022

Flow Tests in Winter 23

Flow Tests in Winter 23


Introduction 

Low code flow testing became Generally Available in the Winter 23 release of Salesforce. Currently limited to record triggered flows, and excluding record deletes, we now have a mechanism to test flows without having to manually execute them every time.

Of course we've always been able to include flow processing in Apex tests - in fact we had no choice. If a record was saved to the database, then all the configured automation happened whether we liked it or not. What we couldn't accurately test was whether the state of the system after the test completed was down to the flow, or something else that happened as part of the transaction. (Incidentally, this is why you shouldn't put logic in triggers - you can only test that by committing a transaction, which brings in all the other automation that has the potential to break your test). Now we can isolate the flow - although not as much as you might want to it turns out.

In this post I'm mainly focusing on what is missing from flow testing, as I'm comparing it to Apex unit testing which is obviously far more mature. While this might read as negative, it really isn't - I think it's great that flows are getting their own testing mechanism - something I've been demanding for a while!

To try this out, I've created a few tests against the Book Order Count flow from the process automation superset - this runs when a new order is received from a contact, iterating the contents of all of their orders and calculating the total number of books bought over their customer lifetime. 

Lack of Isolation

While record triggered flow tests are isolated from the need to write records to the database, they aren't isolated from the contents of the database. In one way this is a good thing - if I want to execute my flow with an order containing line items, I need to use an existing record as the test only allows me to supply fields for a test parent record, not create child records. In every other way, this is not a good thing. If I go this route my test relies on specific records being present in the database, so they'll fail if executed in a brand new org. It also relies on the specific records not changing, which is entirely out of my control and thus makes my test very fragile. 

Even when I'm not using line items, I find myself relying on existing data - to create an empty book order I need to identify a contact that exists in the system, instead of creating a contact as part of the test that is then discarded. This also leads to fragility - for example, I created a new contact named Testy McTest and used this in a test to confirm that if there are no orders for the contact the flow correctly identifies this. Here's a screenshot of my test passing - job done!


However, at some point in time another user running a manual test (or possibly my Evil Co-Worker, who has realised that randomly adding new data can mess up lots of tests) creates an order for Testy with a single book:


I'm blissfully ignorant of this, right up until I run my test again - knowing my luck in front of the steering committee to show the investment was worthwhile, and it no longer works:


Nothing in the flow has changed, but the data in the system that it relied on has, and that is entirely out of my control. 

For anything other than simple flows, I think right now you'd have to be looking at running flow tests in a dedicated scratch org or sandbox that you control the contents of and access to. 

Open the Box

Flow testing has the interesting concept of asserting that a node was visited rather than verifying the outputs are consistent with the node being visited. I can understand where this comes from - in Apex you can unit test the fine detail logic quite easily, as long as you've followed best practice around separation of concerns and functional decomposition, but in flows it's a lot more difficult as the UI doesn't really help you identify the relationships between subflows and parents. Understanding it doesn't mean I like it though - this is an example of Open Box Testing, where the test knows the intimate details of the implementation that it is testing. Open Box Testing does have some advantages, but the big disadvantage is that it tightly couples the test to the item being tested. If the logic needs to change and that involves removing nodes, you are likely to have to revisit your tests and remove your asserts around those nodes, whereas if you've used closed box techniques and are simply asserting the outcome, the entire implementation can change and your tests don't care.

Asserting 

Checking the contents of complex variables managed by the flow, collections for example,  also seems a little tricky. Right now I think I'd have to add variables to the flow to represent things like an empty collection, a record that I want to look for in the collection, or to store the length so that I can verify how many records I have. This is something that I really don't like to do - change my implementation purely for the benefit of the testing framework.

This is pretty much for the same reason as the lack of isolation I mentioned above - I don't have the ability to create variables in the test, so I have to compare against items that already exist. 

Launching from External Tools

I've scoured the docs around flow tests and the APIs, and I can't find any way to launch a flow test from an external tool like the Salesforce CLI. In my view this is something that will hold back adoption - if the only way include this in our CI/CD processes is something like Selenium driving the UI, it all becomes a bit too clunky and we'll likely continue with the Apex testing approach, in spite of the limitations. I'd love to have missed this, so if that is the case please let me know and I'll gladly add a mea culpa update.

Conclusion

As I said earlier, this post has mainly been calling out what I can't do in flow testing, based on what I'm used to doing with test frameworks for programming languages like Apex, Java and JavaScript. I'm sure that flow testing will continue to receive significant investment and will become much more powerful as the releases roll by. Even though it is of limited benefit right now, the benefit is still tangible and you'll be much better off with flow tests and without them. 

Related Posts


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