0

I have a flow that generates a random string to update a field. My problem I'm having is that the test class method isn't showing up when I run the test, so I have 0% coverage and the test method isn't showing up in the drop down for "Code Coverage: None".

Test test does run successfully, however.

Class:

public with sharing class GenerateToken {
    @InvocableMethod
    
    public static void GenerateEmailToken(list<Account> accList){
        integer len = 16;
        blob blobkey = crypto.generateAesKey(128);
        String key = EncodingUtil.convertToHex(blobKey);
        String token = key.substring(0,len);
        System.debug('Token is ' + token);
        
        accList[0].put('CX_Registration_Token__pc', token);
        update accList;
     }
}

Test Class:

@isTest
public with sharing class GenerateTokenTest {
    
    @isTest    
    Public Static void GenerateEmailToken(){
                
        list<Account> accList = new List<Account>();
        
        integer len = 16;
        blob blobkey = crypto.generateAesKey(128);
        String key = EncodingUtil.convertToHex(blobKey);
        String token = key.substring(0,len);
        
        Account pa = new Account(
                                FirstName='TestAccount2',
                                LastName='TestAccount2', 
                                RecordTypeId = Schema.SObjectType.Account.getRecordTypeInfosByName().get('Person Account').getRecordTypeId(),
                                FinServ__LastFourDigitSSN__pc = '0000',
                                PersonBirthdate = Date.valueOf('2000-01-01')
                               
                            );

        insert pa;
    
        accList.add(pa);
        Test.startTest();
        
        pa.CX_Registration_Token__pc = token;        
        update pa;
        
        System.assertEquals(token,pa.CX_Registration_Token__pc);
        Test.stopTest();
    }
   
    
}
7
  • When you say "the test class method isn't showing up," exactly what do you mean - where is it not showing up?
    – Moonpie
    Jun 22, 2021 at 21:26
  • When I go to Test > New Run > select "GenerateTokenTest" > "GenerateEmailToken" > Run. I get the "Success" message, and when I go to "Code Coverage" drop down at the top left, I don't have the method listed there.
    – tDactyl
    Jun 22, 2021 at 21:30
  • 1
    So in the Developer Console, then. I misunderstood - I thought you meant that you could not even select that test method to run it. Are you going to the GenerateEmailToken class tab to click the "Code Coverage" drop down? Or are you doing that on the test class tab?
    – Moonpie
    Jun 22, 2021 at 21:35
  • 2
    From where is your original GenerateEmailToken class called? (E.g., it is in a trigger helper, it is called from a triggered Flow.) Unless it is somehow called via some sort of trigger when an Account is inserted and/or updated, then it will never be touched when you run that test.
    – Moonpie
    Jun 22, 2021 at 22:12
  • I don't think your test class is doing what you think it's doing. You're setting the token in your test class, then asserting it's the value you set it to. Of course that's going to pass. I'm guessing your test class isn't supposed to be setting the token, and the token should be set by your flow. Take out the token assignment from your test, it will then fail. At that point you'll have a proper question to ask.
    – Nick Cook
    Jun 22, 2021 at 22:59

1 Answer 1

3

To gain coverage, you need to cause the code you want coverage for to be executed as part of a test method. If you aren't getting coverage, then your code isn't being executed.

Your focus here shouldn't really be on how your method ends up getting called, but rather on guaranteeing that it is called. In many cases, there's nothing special about testing <thing X here>. It's really as simple as calling your method in your test like it were any other piece of code.

GenerateToken.GenerateEmailToken(myListOfTestAccounts);

If that line (or one like it) doesn't appear in your tests (and you should have more than one test for this class), then you're not testing the GenerateToken class1

Your existing test also goes too far in setting up the test data. You've basically copied the logic you're trying to test into the test itself (instead of using the test to stress your logic). Your unit tests should only do 3 basic things:

  1. Create test data (enough so that the thing you want to test can actually be run)
  2. Execute the code you want to test
  3. Gather the results of the execution, and make assertions to verify that your code behaved the way you expected it to

By creating an Account record, you've taken care of step 1.
Step 2 is accomplished by executing that line of code I provided above.
For step 3, you have to consider the output of running your code.

If your code returns data, then that's at least part of the output. If your code creates, modifies, or deletes record(s), then that's output. In this particular case, given the code you have, you can't possibly know what the token will be. Instead, your output is the fact that CX_Registration_Token__pc has changed. It was one value (possibly null), and after running this code it's a different value. That change is what you're looking for, and it's the entire reason for your test.

Putting that all together, a single test method might look like this:

@isTest
static void myTest(){
    insert new Account(
        Name = 'test Account'
    );

    // To detect a change, you need to get both the "before" and "after" states
    // Can't very well get the "before" state after your code runs, so this is the
    //   place to do it
    // Using a map here will make it easy to tie the before and after states
    //   of the record together
    Map<Id, Account> accountsBefore = new Map<Id, Account>([SELECT Id, CX_Registration_Token__pc FROM Account]);

    Test.startTest();
    GenerateToken.GenerateEmailToken(accountsBefore.values());
    Test.stopTest();

    // Time to gather results
    // With SObject records, that generally means querying for them again
    Map<Id, Account> accountsAfter = new Map<Id, Account>([SELECT Id, CX_Registration_Token__pc FROM Account]);

    // Assertions are the most important part of unit testing
    for(Account acct :accountsAfter.values()){
        // This line here is why it was important to use a map when tracking
        //   the original account state
        Account originalData = accountsBefore.get(acct.Id);

        // The third parameter here is a message that you will be given if
        //   the assertion fails (and only when it fails).
        // It's not mandatory, but it can be very helpful in figuring out what
        //   the issue is (and what it is not).
        System.assertNotEquals(null, acct.CX_Registration_Token__pc, 'Account token is null');
        System.assertNotEquals(originalData.CX_Registration_Token__pc, acct.CX_Registration_Token__pc, 'Account token was not updated');
    }
}

Since you said in comments that this happens after a user clicks a button, it's probably not the end of the world that your code is only operating on index 0 of the list, but I'd still think it'd be good practice to assume that you should always work on the entire list. You may not need it today, but the cost to add that should be low (and you'll appreciate not having to rewrite code when someone decides that you need a way to reset everyone's tokens en masse).

If this is part of a process builder or record-triggered flow, then it would also be prudent to write tests to ensure that the code is only run when the appropriate criteria are met. That's blurring the line between unit and integration testing a bit, but there'd certainly be merit in testing that.

1: Well... it can be called indirectly, like through a flow triggered by a DML operation, but that's closer to integration testing than it is unit testing. Both have their place.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.