Writing Test Classes for Invocable Methods and Invocable Variables

If you need a primer on invocable methods or invocable variables, check out this blog. To keep things consistent, this post references the same Product Selector pro-bono project for Playworks.

Here is the inner class of invocable variables.

public class Requests{
        @invocableVariable(label='Product Id' required=true)
        public String productId;
        @invocableVariable(label='Opportunity Id' required=true)
        public String opportunityId;
        @invocableVariable(label='Sales Price' required=true)
        public Double salesPrice;
        @invocableVariable(label='Quantity' required=true)
        public Integer quantity;

And here is the invocable method, which takes a list of Requests objects as an input parameter.

@InvocableMethod(label='Create New Opportunity Products')
public static void createNewOpportunityProducts(List<Requests> requests){
     Requests request = requests[0];
     List<OpportunityLineItem> oppProds = new List<OpportunityLineItem>();
     Opportunity oppty = [SELECT Playworks_Fiscal_Date__c 
                          FROM Opportunity                           
                          WHERE Id =: request.opportunityId]; 
     PriceBook2 stdPriceBook = [SELECT Id 
                                FROM Pricebook2                                  
                                WHERE isStandard = True 
                                LIMIT 1];       
     PriceBookEntry pbe = [SELECT Id 
                           FROM PriceBookEntry 
                           WHERE Product2Id =: request.productId 
                           AND Pricebook2Id =: stdPriceBook.Id
                           LIMIT 1];       
     for(Integer i = 0; i < request.quantity ; i++){
         OpportunityLineItem oppProd = new OpportunityLineItem();
         oppProd.Product2Id = request.productId;
         oppProd.PricebookEntryId = pbe.Id;
         oppProd.OpportunityId = request.opportunityId;
         oppProd.UnitPrice = request.salesPrice;
         oppProd.Quantity = 1;
     insert oppProds;

Note: The way that this Product Selector flow is designed, only one instance of the Requests class is ever populated. Hence index 0 from List<Requests> is assigned.

Requests request = requests[0];

Typically, Apex tests follow the same basic algorithm.

  • Create the data
  • Run the test
  • Validate the assertions

When writing a test for an invocable method with invocable attributes, there are a couple of things to look out for.

Creating the Data

When the createNewOpportunityProducts invocable method is called by the flow, the flow outputs provide the data. Specifically, the flow maps its output variables to what will become attributes of the Requests object at runtime.

The test class is much more manual. Since there is no flow to pass its output variables, we have to create the necessary data.

Pricebook2 standardPricebook = new Pricebook2();
standardPricebook.Id = Test.getStandardPricebookId();
standardPricebook.isActive = true;
update standardPricebook;
Product2 prod = new Product2();
prod.Name = 'Test Prod';
prod.IsActive = True;
prod.Product_Selector__c = True;
insert prod;
PricebookEntry pbe = new PricebookEntry();    
pbe.Pricebook2Id = standardPricebook.Id;
pbe.Product2Id = prod.Id;
pbe.IsActive = True;
pbe.UnitPrice = 1000;
insert pbe;
Opportunity opp = new Opportunity();
opp.Name = 'Test Oppty';
opp.StageName = 'Prospecting';
opp.CloseDate = Date.today();
insert opp;

Once the data is ready to map, we instantiate an object of the Requests class. To do this, we start with the name of the class that contains invocable method: ProductSelector_Create. That is followed by dot notation to reference the inner class Requests.

List<ProductSelector_Create.Requests> lpscr = new List<ProductSelector_Create.Requests>();

Remember, only one instance of the Requests class is ever populated. So next we instantiate a single ProductSelector_Create.Requests object, map the data that we created earlier to its attributes, and add it to the list of ProductSelector_Create.Requests.

ProductSelector_Create.Requests pscr = new ProductSelector_Create.Requests();
pscr.productId = prod.Id;
pscr.opportunityId = opp.Id;
pscr.salesPrice = 800;
pscr.quantity = 2;

Now we have an input parameter to pass into the createNewOpportunityProducts invocable method.

Running the Test

There is one more thing to be aware of. When an Apex class is called by an Apex trigger, the DML causes the trigger to fire. Since we’re testing an invocable method that’ll be called by a screen flow, no such mechanism exists. Instead of using a DML, we have to explicitly invoke the method, passing in the input parameter we just created.


Validating the Assertions

Thankfully, there are no real gotchas here. In the Product Selector example, verify that the quantity of 2 caused 2 opportunity products to be created.

Running Screen Flows in Slack

With Winter ’23, it’s possible to send screen flows to a Slack user or channel, and enable them to run the flows without leaving Slack.

There are two great use cases for enabling your screen flows to run in Slack:

  1. I you have users who spend time in Slack, they may want to have a Slack experience and you can give them one without having to maintain two different versions of a workflow
  2. You want to build a no-code Slack application

In Winter ’23, the initial supported pattern involves sending/dispatching/pushing a screen flow to a slack destination:

In the future there will be other ways to launch screen flows in Slack, such as via Shortcuts.

Official Documentation

Before we go further, make sure you’re aware of these official docs:

‘Salesforce in Slack’ Setup

Connecting ‘Salesforce in Slack’ to a Salesforce Org

Flow in Slack (Official Doc Home Page)

Create a Flow to Run in Slack

Flow Core Actions for Slack: Send Message to Launch Flow

Verifying Your Basic Slack/Salesforce Integration

It’s ESSENTIAL that you verify that you basic connectivity between your Slack accounts and your Salesforce org is fully and properly configured BEFORE you start dispatching screen flows.

These are the signs of a properly configured infrastructure:

1. Salesforce for Slack, the main Salesforce Slack App, is visible in the App list of any user who wants to run screen flows from Slack

If you don’t see this? Look around the Slack App Directory. Here are a couple of places it lives:

and also take a look at the Initial Slack Setup page in Salesforce Setup:

2. When you click on the Home tab of Salesforce for Slack app in your Slack desktop, you do NOT see a Connect button like the example on the right:

If you see that button, and you want to enable the logged-in Slack account to work with screen flows, you need to set up the connection. Keep in mind that simply installing Salesforce for Slack does not automatically register all of the Slack users on that workspace. They each have to individually log into a salesforce org and approve a connection between Slack and Salesforce.

3. Once the connection is created, verify in User Settings (NOT Setup) that a mapping exists between a Salesforce User and a Slack Account, like this:

From a Flow point of view, you target a Slack account for a flow by having a mapping from that Slack account to a Salesforce User, as shown above, and taking that user’s recordID (you DON’T use username or the channel IDs)

How to Build

With that taken care of, we can can start using screen flows in Slack

Part 1: Creating a Slack-Ready Screen Flow

  1. Create a Screen Flow and check the ‘Make available in Slack (Beta)’ checkbox, available in the Advanced section of Flow Settings

Checking the ‘Make available in Slack (Beta)’ checkbox filters the Screen Builder to show only the elements that currently can be rendered in Slack:

Expect more components to be available in future releases.

Save the screen flow and don’t forget to activate it.

Part 2: Setting Up Your Dispatch

The initial paradigm for Flow in Slack is that you ‘dispatch’ or ‘send’ the screen flow to a specific Slack channel from a flow. Later there will be ways to start screen flows from the Slack side via shortcuts and the like.

Every time you create a Slack-enabled screen flow, a unique ‘SendMessageToLaunchFlow action is generated to send it into Slack.

This is similar to how special actions are generated for each Email Alert and each Quick Action your org defines.

Configuring the Dispatch Action

Here’s what a configured Send Message To Launch Flow action looks like:

Input PropertyNotes
Slack AppIf your org has properly enabled Slack Integration you should see a choice ‘Salesforce for Slack’. Choosing anything other than that is for experts.
Slack WorkspaceThis will show the workspaces that have 1) installed the specified Slack App and 2) connected it to your org
Execute Action AsThis should generally be set to Slack App. If a flow is running in User Context and not System Context, you can choose to have the message sent to Slack by the running User, as opposed to the App.
Slack Conversation IDThis label is in the process of being renamed because you only use Conversation ID’s if you want to send the flow to a group channel. If you’re sending to an individual, use the recordID of their Salesforce User account (example: 005B0000003c7pNIAQ)
Slack Message Allows you to customize the initial launch message.
Button LabelAllows you to customize the initial launch message.
Slack Bot NameThis will change the label that posts the launch message. See Slack Bot Name, below.
Slack TimestampSlack timestamps act as unique identifiers for messages. If you pass in a valid message timestamp, the Flow launch message will show up as a response to that message.

Slack Bot Name

If no value is provide, the App Name (“Salesforce for Slack”) is used

Let Us Know

Have you built a Slack solution using Flow? Share it with us here in the comments!

Guides to Flow and Flow Orchestration at DF ’22

Here’s the Trail Map of Flow-centric Activities:

The links don’t work in these images, but you can search for them here.

For Flow Orchestration, focus on the following:

Advanced ‘Change Owner’ Configuration Now Available In Flow and Apex

This new ChangeOwner action provides advanced automation control over how related objects get handled when a records owner is changed.

Flow already makes short work of changing the owner of a record; a single Update Record is all it takes. But elsewhere in Salesforce, there’s support for more sophisticated ownership change control. For example, when you change the owner of a Case or an Opportunity, you get an additional choices and two related changes:

When you change the owner of an Account from a list view, you get a large number of choices:

Change Owner in Style is a Flow Action that gives you the full power of all of those Salesforce “Owner Change Options”. One of the best ways to see what you can do is to run the test flow that comes packaged with the action. Here’s the main screen:

Now, the screen above is somewhat artificial because it’s designed to show every possible option. In practice, you’ll usually know which object type you’re dealing with, and won’t need to expose all of the possibilities that the action supports. In many cases, you won’t need to expose any UI at all.

How It Works

You need to pass in at least one of:

  • a record
  • a record collection
  • a recordId

Those are set here:

  1. You need to pass in a User Id to the newOwnerId input. You can use a Lookup component for this (see the video above)
  2. You need a Remote Site Setting (The Change Owner action calls a public Salesforce API endpoint so you need to allow your org to essentially call itself.)

Most of the rest of the inputs are boolean inputs that take either true or false, but a few take specific values

The definitive guide to what works is here.

Important Limitations and Considerations

Note that Send Email Notification, which shows up in the Change Owner modal dialogs in the LIghtning UI is not supported here, as it is not exposed in the API for unknown reasons. Use another action to generate an email if you need this capability.

“Also transfer Notes, Attachments and Google Docs” doesn’t seem to be working at the API level.

There are a couple of inputs where, if you want to set them to true, you need to also set some other input to true. Those are:

TransferOtherOpenOpportunitiesIn order for this to work TransferOwnedOpenOpportunities must ALSO be true
TransferAllOwnedCasesIn order for this to work TransferOwnedOpenCases must ALSO be true

Using This Action Directly From Apex

All invocable actions, like the one demonstrated here, can now be invoked directly from Apex code, without requiring any flows, as of Winter ’23. Learn more.


Version 1.0 9/12/22 Production Sandbox Initial Release

View Source


Developer Notes

This action constructs a SOAP call from within Apex, allowing access to properties that exist only in SOAP headers. This is the full list:

This approach can be used to create invocable actions for these other SOAP use cases.

Introducing: User-aware Record Type Picker for Flows

Update September 26: install links updated to latest version

Next week is the year’s big Main Event: Dreamforce. For some of us, that means packing our things and scrambling to fill our session schedules, make meetup plans, and find the perfect dinner spot. For others, like me, an sense of impending FOMO.

Fortunately, the announcements made at Dreamforce, and the Winter 23 release just a few weeks after, will be for everyone, whether you make it in to San Francisco or not. If you’ve reviewed the release notes or scoped out your preview sandbox, one item you may notice is that the Dynamic Fields feature for Screen Flows will be Generally Available, and with that, picklist fields will be Record Type-aware. This means you can simply place a record variable’s picklist field on a screen, and if you’ve set the Record Type for that record, it will correctly display only the picklist values that are active for that Record Type. However, that Record Type must be set on the record variable before the Flow arrives on that Screen with the picklist. So, then: what options do we have today for letting a user select a Record Type?

Today’s Options for RecordType Selection in Flows

  • You could build a Choice for each Record Type. That might work, but what if different users have a different permissions to different Record Types. Sounds like a pretty hairy mess to try to get right, and to maintain moving forward. Not a great option.
  • You could do a Record Choice Set or a Collection Choice Set based on the RecordType object. Of course! Perhaps by setting the Screen Flow to run in User Mode, which respects a user’s access to records, we’ll only get the Record Types that the user has permissions for. If you’ve tried this, you’ll quickly learn that the RecordType object, which holds all each object’s Record Types, is not limited by the user’s permissions. If a user has read access to an object, they’ll have access to every RecordType for that object, even if they don’t have permission to create a record for that Record Type.
  • The closest I could find is the GetRecordTypeInfo action in the UnofficialSF Flow Action Pack. Its a nifty action! But its a bit incomplete, as it requires you to pair it with another screen component, it won’t give you the Record Type Descriptions, and the Record Type information is provided in separate text collections, making it challenging to be able to use the selected option downstream in the Flow.

🎉 Welcome to the team, Record Type Picker 🎉

The Record Type Picker is an open-source, free-to-use component that can be used in Screen Flows to give users selectable choices of Record Types, automatically filtered to just the Record Types that the running user has access to!

Three display types

Visual Picker, Radio Group, Picklist, each with optional Show or Hide Descriptions

Introducing: User-aware Record Type Picker for Flows

Optional Auto-Next Navigation on Select

Auto navigate on selection works best with the Visual Picker on a screen that doesn’t have any other inputs and has the footer and/or Next button hidden. When the user clicks on a selection, the Flow will automatically navigate to the next screen. It is also recommended to provide a “Previous” button on the next screen so that the user has a chance change their selection if they’ve made a mistake.

Easy configuration with a Custom Property Editor that has a Preview mode

Bonus Invocable Action

The package also comes bundled with an Invocable Action to get a Collection of RecordTypes available to the running user, in case you’d prefer to use other screen components like Collection Choices or Data Table

Install with Ease

This component does not depend on any other packages, and can be installed with an unlocked, namespaced package using the links below. The namespace means you don’t have to worry about conflicts with anything you’ve already got in your org, and being unlocked gives you the confidence that what you see in the source repo is what you get in the package.

Package version last updated: September 26 2022


There are a handful of features that I’d like to add, but its at the point now that it should be pretty useful for anyone that needs this kind of thing. Take a look, and please add any issues or feature requests to the Issues log in the GitHub repo!

Work Guide Plus adds a work list to the Flow Orchestration Work Guide

Work Guide Plus (“WG+”) is a custom Work Guide component that you can add to record and home pages, similar to how the Orchestration Work Guide works. In addition to being able to display an individual work item, it can show a list of all the work items currently assigned to the running user.

Consider this example, where I have two work items assigned to me that are associated with the Jones Pharma account:

The Out of The Box Work Guide will simply display one of the two, but WG+ will show a list:

Select a work item and click Open Work Item to work on it.

If there’s only 1 work item assigned to the running user, WG+ will simply execute it and not bother with the list.

Screen flows used as step flows MUST have an input text variable named recordId to receive the record Id as context information, if you use Work Guide Plus.

When the screen flow is finished, WG+ will return to the list of Work Items. It has a two second delay to allow Orchestration to update the status of your work items, and this value can be overridden (the idea here is that if you return to your list too quickly, you might still see the work item you just finished work on because orchestration is still in process).

As a side note, this component features the new official Datatable component, which is Beta in Winter ’23. Because of that, however, it requires a Winter ’23 org. We’ll publish an installable package as soon as Winter ’23 becomes generally available. In the meantime, you can grab the component from the repository below and add it directly to a Winter ’23 org.


package will be available in October ’22


View Source

At Dreamforce: The 1st Unofficial Unofficial SF SF Meetup

UPDATE: I TESTED POSITIVE FOR COVID THE NIGHT BEFORE DREAMFORCE, SO I WON’T BE AT THIS MEETUP. you might want to go anyways and see who shows, however.

Alex here…. If you’re coming to Dreamforce, drop by and say hello and tell me how you’re using Salesforce Automation and what you want to see in the future. I’ll be hanging out at Moscone West on the 2nd floor in back behind the session rooms, where they serve lunch, on Tuesday between 2:30-3:30pm. Would love to chat!

From Salesforce: 6 New Flow Builder Videos for Beginners

I love it when we get some of those official Salesforce Flow videos with those sweet, sweet production values. This latest set of bite-sized videos is a definite new Must Watch for new Flow creators, drilling down on specific topics like Scheduled Paths and Variables.

The videos are short, but there’s some nice focus: one of them focuses purely on introducing variables, using a lunchbox and thermos metaphor:

And another one focuses purely on Before Save vs After Save triggers:

Check it Out! And find other Flow tutorials here.

Using Flow to Retrieve Google Calendar Events and create a great Alerter App

I tend to get distracted and either miss or forget the meeting alerts that Google Calendar generates, and recent changes to Mac OS notification have made the problem worse, so I decided to build a louder, more insistent alerting application using Flow. When the flow runs, it queries my Google Calendar account every minute, and when a new meeting start time gets close, it starts tracking the meeting and emitting progressively louder and longer alarms. The package provided here installs the Alerter flow but also a new Get Google Calendar Events invocable action that takes a Google Calendar identifier and a named credential that authorizes access to that calendar’s events, and returns a data structure of events.

Here’s an example of the action configuration:

Authentication & Authorization

Figuring out how to configure a Salesforce org with the necessary credentials to make API calls to Google Calendar apis was an adventure in itself. You can read about that here.

Working with the event results

On this output screen, the raw data returned from Google is displayed on the left:

Here’s the Google documentation on the Event List response.

In order to make it easier for the flow to work with the data returned in this big json blob of text, this package defines three new custom types (aka ‘Apex Types’) called GoogleCalendarEventSet, GoogleCalendarEvent, and GoogleDateTime. The action does returns the data from Google as raw JSON but it also passes back a far more practical GoogleCalendarEventSet object that contains a list of GoogleCalendarEvent objects. In the image above, the data on the right looks a little similar, but what you’re actually seeing is the readout of a collection that can be looped over in the flow.


calendarIdentifierthe main identifier for a Google Calendar. See ‘Calendar Identifier’ below
credNamethe name of a Named Credential that has authenticated to a Google account. See this article.
End Time – requires RFC3339 timestamp with mandatory time zone offset, for example, 2011-06-03T10:00:00-07:00. This is optional. If you don’t include it, the number of events you get will be solely determined by Max Results
startTimeString – requires RFC3339 timestamp with mandatory time zone offset, for example, 2011-06-03T10:00:00-07:00. If left null or empty, the current time will be used
maxResultsThe number of results to return. Defaults to 3
orderByThe sort order of the returned results. Allowable values are ‘start time’ and ‘updated’. Updated referes to last modification time (ascending).
queryStringthis will do a free text search on summary, description, location, attendee’s displayName, attendee’s email

For more information, see the official documentation at https://developers.google.com/calendar/api/v3/reference/events/list

Calendar Identifier

Find this ID in Google Calendar settings:


V 1.0 9/11/22 Production Sandbox 1st Release


view source

From Andy Haas: Trigger Screen Flows with Record Changes using ‘Detect and Launch’

Some while back, the Detect and Launch component opened up a new frontier by making it possible to pop a screen flow from a record page automatically when the record is edited or deleted. Andy Haas has extended this component with some valuable new capabilities.

Previously, you could pop a screen flow either 1) when the underlying record is updated/edited or 2) when the record is deleted.

This update adds two features:

  1. Launching a screen flow when a record is edited can now be further configured to make the launch dependent on a particular field value change. Example: you need an agent to fill in specific details when they close a case or your sales reps need to send a quote to the customer you can launch a flow after a specific field has changed and, for example, is equal to true. 
  2. You can now trigger screen flows to run on the Load of a record page, and not just on an Edit or a delete

This demo was produced for V2.0 and shows Conditional Launch Based on a Field Edit:

Triggering a screen flow when a record page loads

Perform this with the new input field ‘Flow Name (when the record is loaded)’. You can use this to launch the flow when the record is opened. Note that if you are using Launch Mode = Modal, you will need to build a close function within the flow, as the modal doesn’t have a close function, yet.

Conditional Screen Flow Launch Based on a field edit

Let’s say you have a case record that you want to launch a flow when the case is closed to get the user to enter the amount of time that they spent on the case. To configure this within Detect and Launch, enter the flow name you want in ‘Flow Name ( when the record is edited )’, set Change Field to ‘Status’, and set Change Value to ‘Closed’. 

Want to check multiple fields and launch different flows based on them? Or run different flows depending on the value of the field? Add multiple Detect and Launches with different criteria to launch different flows.

New Fields

Change FieldIs used to signify what field you want to watch when a record has been edited.
Change ValueUsed to be the comparison value when the record has been edited. 
Flow Name (when the record is Loaded)use this to launch the flow when the record is opened.

Component Configuration Example:


  • At this time, the component does not support cross-field comparisons. Change Value must be a static value. We suggest utilizing formula fields to do complex comparisons that evaluate true or false.
  • Conditional Field-Based Launch only works when editing a record. It does not work on deleting or loading a record.

These features are available in V2.0+ of Detect & Launch