SFDC Stop - Always the latest about Salesforce


Full Tutorial Series with videos, free apps, live sessions, salesforce consulting and much more.


Telegram logo   Join our Telegram Channel

Saturday, 7 May 2022

How to create test class for a flow? | Test coverage for flows | Salesforce Flow Test Class Basics

Hello Trailblazers,


In this post we're going to learn How to create test class for a flow? Most of the developers I've met have never written a test class for flow and some of them are not even aware that we can write a test class for flow and evaluate the test coverage. The question that I get is: Is it possible to create test class for a flow??


YES, THIS IS POSSIBLE!! LET'S SEE HOW?

Create a Flow

First of all, let's create a simple flow. I am sharing the details of the flow below:


Flow Type: Record Triggered Flow

Object: Lead

Trigger the Flow When: A record is created or updated

Entry Conditions: All Conditions Are Met (AND) -> AnnualRevenue -> Is Changed -> True

Optimize the Flow for: Fast Field Updates

Flow Label: Update Lead Rating based on Annual Revenue

Flow Description: This flow will update lead rating based on the value of annual revenue


What this flow is going to do?


Our flow will update the Lead Rating based on the value of Annual Revenue of lead. There are 3 conditions that it'll cover:


  1. Annual Revenue is less than 25,000 -> Lead Rating = Cold

  2. Annual Revenue is between 25,000 and 75,000 -> Lead Rating = Warm

  3. Annual Revenue is greater than 75,000 -> Lead Rating = Hot

So, we created a decision element named "Check Annual Revenue". It'll have 3 outcomes based on the above 3 conditions:

Less than 25,000


Between 25,000 and 75,000


Greater than 75,000 (Default Outcome)


Now, let's create 3 update elements as well:

Update Rating to Cold


Update Rating to Warm


Update Rating to Hot


Now that we have our update elements, we need to link these 3 update elements with our decision outcomes so that, when: 
  • Annual Revenue is less than 25,000 -> Lead Rating = Cold
  • Annual Revenue is between 25,000 and 75,000 -> Lead Rating = Warm
  • Annual Revenue is greater than 75,000 -> Lead Rating = Hot

This is how our final flow looks like:

Create a test class for the above flow

Now, it's time to create a test class for this flow. While creating a test class for flow, The intent is to cover all the flow elements, while testing various scenarios and verify the result. Let's have a look at the full code for the test class below covering all scenarios and then we'll see each method one by one in detail.
/*
* Created:- 07/05/2022
* Last Updated:- 07/05/2022
* Description:- This is the test class for Update_Lead_Rating_based_on_Annual_Revenue Flow
*/
@isTest
public with sharing class LeadUpdateFlowTest {

    // * Constants
    static final Integer NUMBER_OF_RECORDS = 200;
    static final String RATING_COLD = 'Cold';
    static final String RATING_WARM = 'Warm';
    static final String RATING_HOT = 'Hot';

    // * Description: This method is used to create initial data for the test methods
    @TestSetup
    static void makeData() {
        List<Lead> leads = new List<Lead>();
        for(Integer i=1; i<=NUMBER_OF_RECORDS; i++) {
            leads.add(new Lead(LastName = 'Test Lead ' + i, Company='Test Company'));
        }
        insert leads;
    }

    /*
    *   Description: This method is used to verify lead rating
    *   when the annual revenue is less than 25,000
    */
    @isTest
    public static void updateLeadBasedOnAnnualRevenueLessThan25kTest() {
        List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
        Test.startTest();
        for(Lead lead : leads) {
            lead.AnnualRevenue = 24999;
        }
        update leads;
        List<Lead> updatedLeads = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeads) {
            System.assertEquals(RATING_COLD, lead.Rating, 'Lead rating should be: ' + RATING_COLD);
        }
        Test.stopTest();
    }

    /*
    *   Description: This method is used to verify lead rating
    *   when the annual revenue is between 25,000 and 75,000.
    *   We're keeping the value at floor i.e. 25,000 for this test
    */
    @isTest
    public static void updateLeadBasedOnAnnualRevenueBetween25kAnd75kFloorTest() {
        List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
        Test.startTest();
        for(Lead lead : leads) {
            lead.AnnualRevenue = 25000;
        }
        update leads;
        List<Lead> updatedLeads = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeads) {
            System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
        }
        Test.stopTest();
    }

    /*
    *   Description: This method is used to verify lead rating
    *   when the annual revenue is between 25,000 and 75,000.
    *   We're keeping the value at ceiling i.e. 75,000 for this test
    */
    @isTest
    public static void updateLeadBasedOnAnnualRevenueBetween25kAnd75kCeilingTest() {
        List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
        Test.startTest();
        for(Lead lead : leads) {
            lead.AnnualRevenue = 75000;
        }
        update leads;
        List<Lead> updatedLeads = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeads) {
            System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
        }
        Test.stopTest();
    }

    /*
    *   Description: This method is used to verify lead rating
    *   when the annual revenue is greater than 75,000
    */
    @isTest
    public static void updateLeadBasedOnAnnualRevenueGreaterThan75kTest() {
        List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
        Test.startTest();
        for(Lead lead : leads) {
            lead.AnnualRevenue = 75001;
        }
        update leads;
        List<Lead> updatedLeads = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeads) {
            System.assertEquals(RATING_HOT, lead.Rating, 'Lead rating should be: ' + RATING_HOT);
        }
        Test.stopTest();
    }

    /*
    *   Description: This method is used to cover all low elements and verify lead rating
    *   when the annual revenue is less than 25k, between 25k and 75k and also greater than 75k
    */
    @isTest
    public static void updateLeadBasedOnAnnualRevenueAllElementsTest() {
        List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
        Test.startTest();
        for(Lead lead : leads) {
            lead.AnnualRevenue = 20000;
        }
        update leads;
        List<Lead> updatedLeadsWithRatingCold = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeadsWithRatingCold) {
            System.assertEquals(RATING_COLD, lead.Rating, 'Lead rating should be: ' + RATING_COLD);
        }
        for(Lead lead : leads) {
            lead.AnnualRevenue = 50000;
        }
        update leads;
        List<Lead> updatedLeadsWithRatingWarm = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeadsWithRatingWarm) {
            System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
        }
        for(Lead lead : leads) {
            lead.AnnualRevenue = 80000;
        }
        update leads;
        List<Lead> updatedLeadsWithRatingHot = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeadsWithRatingHot) {
            System.assertEquals(RATING_HOT, lead.Rating, 'Lead rating should be: ' + RATING_HOT);
        }
        Test.stopTest();
    }
}
Now let's understand each method one by one in detail:
    // * Constants
    static final Integer NUMBER_OF_RECORDS = 200;
    static final String RATING_COLD = 'Cold';
    static final String RATING_WARM = 'Warm';
    static final String RATING_HOT = 'Hot';
First of all I created some constants where I specified the total number of records my test class will create and also the 3 lead ratings that I need to verify once my flow is executed.
    // * Description: This method is used to create initial data for the test methods
    @TestSetup
    static void makeData() {
        List<Lead> leads = new List<Lead>();
        for(Integer i=1; i<=NUMBER_OF_RECORDS; i++) {
            leads.add(new Lead(LastName = 'Test Lead ' + i, Company='Test Company'));
        }
        insert leads;
    }
Next, I have the makeData() method which is going to create some lead records based on the number we specified in the NUMBER_OF_RECORDS constant.
    /*
    *   Description: This method is used to verify lead rating
    *   when the annual revenue is less than 25,000
    */
    @isTest
    public static void updateLeadBasedOnAnnualRevenueLessThan25kTest() {
        List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
        Test.startTest();
        for(Lead lead : leads) {
            lead.AnnualRevenue = 24999;
        }
        update leads;
        List<Lead> updatedLeads = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeads) {
            System.assertEquals(RATING_COLD, lead.Rating, 'Lead rating should be: ' + RATING_COLD);
        }
        Test.stopTest();
    }
Once we have the records created, the first method that's specified above will test the scenario when we have the annual revenue of every lead less than 25,000. So, we updated all the leads with annual revenue as 24,999, we queried the updated leads and verified the lead rating. As per our flow, the lead rating should be Cold.

Let's run this test method once and see the results:

As you can see, the test executed successfully and passed. But Wait!! How can I check the code coverage? OR I should say the flow elements coverage?

We can do that using a simple soql query:
sfdx force:data:soql:query --query "SELECT Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered, NumElementsNotCovered FROM FlowTestCoverage WHERE FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue' AND TestMethodName = 'updateLeadBasedOnAnnualRevenueLessThan25kTest'" --usetoolingapi

As you can see above, I am querying the Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered and NumElementsNotCovered fields of FlowTestCoverage object and I have also added a where condition where I specified the API name of my flow as: FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue' and I also added a condition for my test method that I executed above as: TestMethodName = 'updateLeadBasedOnAnnualRevenueLessThan25kTest'. Note that I am using Tooling API for this query by adding a flag --usetoolingapi because we're performing a query on the flow metadata.

The output of the above query is provided below:
As you can see above, there are 2 elements of the flow which are covered and 2 elements which are not covered. As per the condition that we checked, we can say that the below two elements of the flow encircled in RED are covered:
and the other two elements are not covered. As we've verified that the flow is updating the Rating to Cold, therefore, we can say that this update element was covered. Let's jump onto the next method now!
    /*
    *   Description: This method is used to verify lead rating
    *   when the annual revenue is between 25,000 and 75,000.
    *   We're keeping the value at floor i.e. 25,000 for this test
    */
    @isTest
    public static void updateLeadBasedOnAnnualRevenueBetween25kAnd75kFloorTest() {
        List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
        Test.startTest();
        for(Lead lead : leads) {
            lead.AnnualRevenue = 25000;
        }
        update leads;
        List<Lead> updatedLeads = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeads) {
            System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
        }
        Test.stopTest();
    }
In the next method that you can see above, we tested the scenario when the annual revenue is between 25,000 and 75,000. We can have 2 edge conditions here i.e. the annual revenue equal to 25,000 and the annual revenue equal to 75,000. We're testing the floor condition here i.e. the minimum value of AnnualRevenue which will satisfy this condition. Therefore, we've set the annual revenue to 25,000 and verified that the rating should be Warm.

The output when the above test method is executed is given below:
    /*
    *   Description: This method is used to verify lead rating
    *   when the annual revenue is between 25,000 and 75,000.
    *   We're keeping the value at ceiling i.e. 75,000 for this test
    */
    @isTest
    public static void updateLeadBasedOnAnnualRevenueBetween25kAnd75kCeilingTest() {
        List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
        Test.startTest();
        for(Lead lead : leads) {
            lead.AnnualRevenue = 75000;
        }
        update leads;
        List<Lead> updatedLeads = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeads) {
            System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
        }
        Test.stopTest();
    }
Next we tested with the ceiling value or the second edge condition where the annual revenue is set to 75,000. As you can see above, we verified that the rating should be Warm in this case as well. 

The output when the above test method is executed is given below:

Let's see the test coverage for these methods as well. The updated query is given below:
sfdx force:data:soql:query --query "SELECT Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered, NumElementsNotCovered FROM FlowTestCoverage WHERE FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue' AND (TestMethodName = 'updateLeadBasedOnAnnualRevenueBetween25kAnd75kFloorTest' OR TestMethodName = 'updateLeadBasedOnAnnualRevenueBetween25kAnd75kCeilingTest')" --usetoolingapi
The result when the above query is executed is shown below:
As you can see above, for both the test method executions, two elements of the flow are covered and two are not covered. This time both the methods are dealing with the condition when annual revenue is between 25,000 and 75,000. Therefore, we can say that the below two elements of the flow encircled in RED are covered:
and the other two elements are not covered from this test method. As we've verified that the flow is updating the Rating to Warm, therefore, we can say that this update element was covered. Let's cover the last update element as well:
    /*
    *   Description: This method is used to verify lead rating
    *   when the annual revenue is greater than 75,000
    */
    @isTest
    public static void updateLeadBasedOnAnnualRevenueGreaterThan75kTest() {
        List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
        Test.startTest();
        for(Lead lead : leads) {
            lead.AnnualRevenue = 75001;
        }
        update leads;
        List<Lead> updatedLeads = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeads) {
            System.assertEquals(RATING_HOT, lead.Rating, 'Lead rating should be: ' + RATING_HOT);
        }
        Test.stopTest();
    }

Finally, the last test condition can be, when the annual revenue is greater than 75,000. So, we updated all the leads with annual revenue as 75,001. We queried the updated leads and verified the lead rating. As per our flow, the lead rating should be Hot.

The output when the above test method is executed is given below:

As you can see above, the method executed successfully, let's see the code coverage for this method as well. I am sharing the updated query below:
sfdx force:data:soql:query --query "SELECT Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered, NumElementsNotCovered FROM FlowTestCoverage WHERE FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue' AND TestMethodName = 'updateLeadBasedOnAnnualRevenueGreaterThan75kTest'" --usetoolingapi
The output when this query is executed is given below:

As you can see above, once again, two elements of the flow are covered and two elements are not covered. This time our method is dealing with the condition when annual revenue is greater than 75,000. Therefore, we can say that the below two elements of the flow encircled in RED are covered:
and the other two elements are not covered from this test method. Again we've verified that the flow is updating the Rating to Hot, therefore, we can say that this update element was covered.

We've now covered all the flow elements using different test methods and the results that we verified in our test methods signify that the right elements were covered. You may be thinking that it would be great if we can cover all the flow elements once, just to be sure that we've covered everything. Well, we can do that as well. Let's have a look at the below method:
    /*
    *   Description: This method is used to cover all low elements and verify lead rating
    *   when the annual revenue is less than 25k, between 25k and 75k and also greater than 75k
    */
    @isTest
    public static void updateLeadBasedOnAnnualRevenueAllElementsTest() {
        List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
        Test.startTest();
        for(Lead lead : leads) {
            lead.AnnualRevenue = 20000;
        }
        update leads;
        List<Lead> updatedLeadsWithRatingCold = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeadsWithRatingCold) {
            System.assertEquals(RATING_COLD, lead.Rating, 'Lead rating should be: ' + RATING_COLD);
        }
        for(Lead lead : leads) {
            lead.AnnualRevenue = 50000;
        }
        update leads;
        List<Lead> updatedLeadsWithRatingWarm = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeadsWithRatingWarm) {
            System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
        }
        for(Lead lead : leads) {
            lead.AnnualRevenue = 80000;
        }
        update leads;
        List<Lead> updatedLeadsWithRatingHot = [SELECT Rating FROM Lead];
        for(Lead lead : updatedLeadsWithRatingHot) {
            System.assertEquals(RATING_HOT, lead.Rating, 'Lead rating should be: ' + RATING_HOT);
        }
        Test.stopTest();
    }
As you can see above, this method is used to cover all the scenarios one by one by updating the leads and will also verify the results of the flow updates once all the lead records are updated.

The output when the above test method is executed is given below:

So, our test passed. Now let's see the code coverage for this method using the updated query shared below:
sfdx force:data:soql:query --query "SELECT Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered, NumElementsNotCovered FROM FlowTestCoverage WHERE FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue' AND TestMethodName = 'updateLeadBasedOnAnnualRevenueAllElementsTest'" --usetoolingapi
The output when this query is executed is given below:

As you can see above, this time the total number of elements covered are 4 and the total number of elements not covered are 0. Therefore, we can say that all the 4 elements of the flow were covered this time which are encircled in RED below:
So, this time, YOU HAVE COVERED THE WHOLE FLOW. CONGRATULATIONS!!!

This is how you can create a test class of the flow and verify the test coverage as well. I am sharing the combined results of all the methods below:
You can get these results by executing the SOQL query as shown below:
sfdx force:data:soql:query --query "SELECT Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered, NumElementsNotCovered FROM FlowTestCoverage WHERE FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue'" --usetoolingapi
We just removed the TestMethodName AND condition here because we want to query the results of all test methods.

Note: If you want to make sure that you deploy all the flows to your production environment with good test coverage, you can go to Setup --> Process Automation Settings and select the Deploy processes and flows as active option. Please note that this option will not be available in non-production orgs. If you turn this on, salesforce will make sure that your apex tests have at least 75% coverage of the total number of active processes and active autolaunched flows in your org. If the required percentage isn't met, the deployment will be rolled back. You can learn more about that here.

Now you can make sure that your flows are also covered while performing the deployments.

So, that's all for this tutorial everyone, I hope you liked it and understood how you can create test class for a flow. Let me know your feedback in the comments down below. 

Happy Trailblazing!!

2 comments:

  1. Thank you for sharing this article, i didn't know you can create an apex test class for Flow

    ReplyDelete