Learn MOAR in Winter ’22 with Apex Enhancements | Salesforce Developers Blog

Follow and complete a Learn MOAR Winter ’22 trailmix for Admins or Developers by October 31 to earn a special community badge and get a chance to win one of five Salesforce Certification vouchers (up to $200USD value). Restrictions apply. Learn how to participate and review the Official Rules by visiting the Learn MOAR quests page.

The Winter ’22 release brings exciting new features for Apex developers. In this post, we’ll give you a preview of three of our favorites: a new Enum valueOf() method, calling invokable actions from Apex, and using mock responses to test Salesforce Functions.

Enum valueOf() method

Let’s start by exploring the new valueOf() method in the Enum class. This method lets you convert a String to an Enum constant value. Before Winter’22, to get an Enum constant value from string, you had to iterate through all the values searching for a matching name. Rather than, for example, having to create a map linking string and Enum constants to do the lookup against, you can simply use Enum.valueOf().

public with sharing class EnumValueOf {

    public enum Season {WINTER, SPRING, SUMMER, FALL}
    
    public static void convertStringToEnumConstant(){
        String currentSeasonInput = 'WINTER';

        // Before - searching the values()
        Map<String, Season> seasonsByName = new Map<String, Season>();
        for (Season enumValue : Season.values())
        {
            seasonsByName.put(enumValue.name().toUpperCase(), enumValue);
        }
        Season currentSeason = seasonsByName.get(currentSeasonInput);

        System.debug('Current season (older technique): ' + currentSeason);

        // With Winter'22
        Season currentSeasonWithValueOf = Season.valueOf(currentSeasonInput);

        System.debug('Current season (new technique): ' + currentSeasonWithValueOf);
    }
}

This feature addresses an IdeaExchange request: Apex Enum parse from string.

Call invocable actions from Apex (developer preview)

The Winter’22 release incorporates the ability to call invocable actions from Apex. Invocable actions are automated actions that can be invoked from different entry points such as the REST API, Flow, or Process Builder. They are one of the best tools in Salesforce to quickly get things done. From Winter’22, you will be able to invoke them in Apex. This adds one more tool to the Apex developer toolset, making it easier and quicker to implement certain types of automation, to reuse code, and it builds one more bridge between low-code and pro-code development.

Bear in mind that this feature is in developer preview. You can activate it on scratch orgs by including the CallIAFromApex feature in the scratch org configuration file.

{
  "orgName": "Winter'22 Examples",
  "edition": "Developer",
  "features": ["CallIAFromApex"],
}

Let’s take a look at some examples. First, let’s explore how to invoke standard actions. Every button and link in Salesforce can be considered a standard action; take a look at a complete list of available standard actions. In this example we invoke a standard invocable action to create a Chatter post:

public static void callStandardInvocableAction(){
    Invocable.Action action = Invocable.Action.createStandardAction('chatterPost');
    action.setInvocationParameter('text', 'I love the new Developer Website!');
    action.setInvocationParameter('type', 'User');
    action.setInvocationParameter('subjectNameOrId', UserInfo.getUserId());

    List<Invocable.Action.Result> results = action.invoke();

    if (results.size() > 0 && results[0].isSuccess()) {
            System.debug('Feed Item Id:' + results[0].getOutputParameters().get('feedItemId'));
    }
}

To call the action, we first instantiate the createStandardAction method from the new Invocable.Action Apex class. Then, we set the invocation parameters, and finally, we invoke it calling the invoke method. The method returns a list of Invocable.Action.Result that you can explore to find out if the operation succeeded or failed or retrieve the output.

This feature is not restricted to standard invocable actions; you can call custom invocable actions too. Custom invocable actions are defined in Apex. You specify the inputs, outputs, and what the method will do. For instance, here we have a custom invocable action that receives a mascot name as a parameter and returns a String, building a sentence:

public with sharing class SampleInvocableAction {
    @InvocableMethod(label='Construct Sentence')
    public static List<String> tellMeSomething(List<String> mascot) {
        String mascotName = mascot == null || mascot.isEmpty() || String.isBlank(mascot[0]) ? 'Appy' : mascot[0];
        return new List<String>{mascotName + ' thinks the new Developer Website is amazing!!'};
    }
}

Now we can instantiate the action with createCustomAction, set the expected parameter, and invoke it:

public static void callCustomInvocableAction(){
    Invocable.Action action = Invocable.Action.createCustomAction('apex', 'SampleInvocableAction');
    action.setInvocationParameter('mascot', 'Codey');

    List<Invocable.Action.Result> results = action.invoke();

    if (results.size() > 0 && results[0].isSuccess()) {
        System.debug('Result is: ' + results[0].getOutputParameters().get('output'));
    }
}

Take a look at all the methods available for the new Invocable.Action, Invocable.Action.Result and Invocable.Action.Error classes on the release notes!

Use mock responses to test Salesforce Functions

Finally, let’s go into our third topic, our beloved Functions! Functions are scheduled to become Generally Available in the Winter’22 release. As part of this release, we’ve created some classes and interfaces to help you to easily write Apex tests for function invocations.

Let’s take a look at some examples. We’ll use an arbitrary function just to illustrate how testing works. In fact, the code that we use here doesn’t matter as the mock will be responding during test execution rather than the underlying function.

module.exports = async function (event, context, logger) {
    // Do something here
}

To test the invocation of a function, you’ll have to provide a mock implementation for the function. The mock class needs to implement the new functions.FunctionInvokeMock interface and specify the mock response sent to the respond() method when the Apex runtime calls the function. By using the createSuccessResponse() and createErrorResponse() methods in the mock class, you can emulate the success or failure of the function invocation.

public class FunctionsInvokeMockInner implements functions.FunctionInvokeMock {
        public Boolean throwError = false;
        private String invocationId = '000000000000000';

        public functions.FunctionInvocation respond(String functionName, String payload) {
            if(throwError) {
                return functions.MockFunctionInvocationFactory.createErrorResponse(
                    invocationId,
                    functions.FunctionErrorType.FUNCTION_EXCEPTION,
                    'Function returned an error!');
            }
    
            return functions.MockFunctionInvocationFactory.createSuccessResponse(invocationId, 'Success!');
       }
    }

In this sample mock, by default we emulate a successful response. If the throwError variable is true, then we emulate an error.

Let’s take a look at a method in the SampleFunctionCall class that invokes the sample function synchronously:

public static void callFunctionSynchronously() {
    // Call Function Synchronously
    functions.Function myFunction = functions.Function.get('example.myfunction');
    functions.FunctionInvocation invokeResult = myFunction.invoke('{}');

    // Post to Chatter if error
    postToChatter(invokeResult);
}

The postToChatter() method posts a message to Chatter, indicating if the function executed successfully or if an error happened:

private static void postToChatter(functions.FunctionInvocation invokeResult) {
    FeedItem post = new FeedItem();
    post.ParentId = UserInfo.getUserId();
    if (invokeResult.getStatus() == functions.FunctionInvocationStatus.ERROR) {
        post.Title = 'Error while processing Function';
        post.Body = 'Error: ' + invokeResult.getError().getMessage();
    } else {
        post.Title = 'Function executed successfully';
        post.Body = 'Invocation ID: ' + invokeResult.getInvocationId();
    }

    insert post;
}

This is how we can test the callFunctionSynchronously() method:

@isTest
static void testSyncFunctionCall() {
    // Set mock class to respond to function invocations
    Test.setMock(functions.FunctionInvokeMock.class, new FunctionsInvokeMockInner());

    // Synchronous function call
    Test.startTest();
    SampleFunctionCall.callFunctionSynchronously();
    Test.stopTest();

    // Verify success Chatter post has been created
    List<FeedItem> posts = [SELECT Title, Body FROM FeedItem ORDER BY CreatedDate DESC LIMIT 1];
    System.assertEquals(1, posts.size());
    FeedItem post = posts[0];
    System.assertEquals('Function executed successfully', post.Title);
    System.assertEquals('Invocation ID: 000000000000000', post.Body);
}

In the example we instruct the Apex runtime to send the fake response by calling Test.setMock(). The first argument to Test.setMock() is the functions.FunctionInvokeMock.class, and the second argument is a new instance of your implementation of the functions.FunctionInvokeMock interface.

When the Test.setMock() method is called, the real function is not actually invoked. Instead, the mock response that you specified in the respond() method is received instead. In this case, we mocked a successful invocation, so a success Chatter post should have been created.

In the case of invoking a function asynchronously, you’ll have to specify a callback to process the function output, as seen in this second example:

public static void callFunctionAsynchronously() {
    // Call Function Asynchronously
    functions.Function myFunction = functions.Function.get('example.myfunction');
    myFunction.invoke('{}', new SampleFunctionCallback());
}

The callFunctionAsynchronously() method, reuses the postToChatter() method showed above.

public with sharing class SampleFunctionCallback implements functions.FunctionCallback {
    public void handleResponse(functions.FunctionInvocation invokeResult) { 
        postToChatter(invokeResult);
    }
}

We can test the async function invocation in the same way we did for the sync one.

@isTest
static void testAsyncFunctionCallError() {
    // Set mock class to respond to function invocations
    FunctionsInvokeMockInner mock = new FunctionsInvokeMockInner();
    mock.throwError = true;
    Test.setMock(functions.FunctionInvokeMock.class, mock);

    //Asynchronous function invocation with callback
    Test.startTest();
    SampleFunctionCall.callFunctionAsynchronously();
    Test.stopTest();

    // Verify Chatter post has been created
    List<FeedItem> posts = [SELECT Title, Body FROM FeedItem ORDER BY CreatedDate DESC LIMIT 1];
    System.assertEquals(1, posts.size());
    FeedItem post = posts[0];
    System.assertEquals('Error while processing Function', post.Title);
    System.assertEquals('Error: Function returned an error!', post.Body);
}

In this example, we force the error (by setting throwError to true), so that the function mock fires an error. Then we check that the error Chatter post has been created.

Now you have all the required tooling to start writing Functions! Take a look at our Functions Recipes sample app to learn more.

Next steps

If you want to take a deeper look at these new Apex features coming in Winter ‘22, check the rr-winter22 GitHub repository.

About the author

Alba Rivas works as a Principal Developer Advocate at Salesforce. She focuses on Lightning Web Components and Lightning adoption strategy. You can follow her on Twitter @AlbaSFDC.

Stay up to date with the latest news from the Salesforce Developers Blog

Subscribe