Skip to main content

#Trailhead460 personnes en discutent

Hi Salesforce Community,

I have created an Apex class named SingleEmailLimitController to check the daily single-email limit in my Salesforce organization. The class retrieves the email limit using System.OrgLimits.getMap() and returns the daily limit, emails used, emails remaining, and a status message.

I am trying to write an Apex test class to achieve 100% code coverage, but I am facing difficulties covering all the conditional branches and the exception-handling block.

My main challenges are:

  1. Testing the scenario where limits.get('SingleEmail') returns null.
  2. Covering the condition where the daily email limit has been reached or exceeded.
  3. Covering the condition where 10 or fewer emails remain.
  4. Covering the condition where sufficient email capacity remains.
  5. Covering the catch (Exception e) block.
  6. Testing these scenarios without depending on the actual email usage and limits of the Salesforce organization.

Since System.OrgLimits.getMap() returns system-provided limit information, I am unsure how to simulate these different scenarios in an Apex test.

My Apex controller:

public with sharing class SingleEmailLimitController {

public class EmailLimitResponse {

@InvocableVariable(label='Daily Single Email Limit')

public Integer dailyLimit;

@InvocableVariable(label='Emails Used')

public Integer emailsUsed;

@InvocableVariable(label='Emails Remaining')

public Integer emailsRemaining;

@InvocableVariable(label='Limit Status Message')

public String statusMessage;

}

@InvocableMethod(

label='Check Single Email Limit'

description='Checks daily email usage and returns a status message.'

)

public static List<EmailLimitResponse> checkSingleEmailLimit(

List<String> requests

) {

EmailLimitResponse result = new EmailLimitResponse();

List<EmailLimitResponse> results =

new List<EmailLimitResponse>();

try {

Map<String, System.OrgLimit> limits =

System.OrgLimits.getMap();

System.OrgLimit emailLimit = limits.get('SingleEmail');

if (emailLimit == null) {

result.statusMessage =

'Unable to retrieve the daily email limit.';

} else {

result.dailyLimit = emailLimit.getLimit();

result.emailsUsed = emailLimit.getValue();

result.emailsRemaining =

Math.max(

0,

result.dailyLimit - result.emailsUsed

);

if (result.emailsUsed >= result.dailyLimit) {

result.statusMessage =

'Daily email limit exceeded. No email capacity remains.';

} else if (result.emailsRemaining <= 10) {

result.statusMessage =

'Daily email limit almost reached. Only ' +

result.emailsRemaining + ' emails remaining.';

} else {

result.statusMessage =

'Email capacity available. ' +

result.emailsRemaining +

' emails remaining out of ' +

result.dailyLimit + '.';

}

}

} catch (Exception e) {

result.statusMessage =

'Unable to check email limit: ' + e.getMessage();

}

results.add(result);

return results;

}

}

My questions:

  • Is it possible to achieve 100% code coverage for this controller without changing its implementation?
  • If not, what is the recommended approach for making the limit values testable?
  • How can I write a complete Apex test class that covers all the conditional branches and the exception-handling block?
  • Would using a @TestVisible helper method or an injectable service be a suitable solution?

Please share a working example of the controller changes, if necessary, and the corresponding Apex test class.

Thank you for your help! 

 

#Salesforce Developer  #Salesforce Admin  #Trailhead Challenges  #Trailhead

1 réponse
  1. 16 oct., 15:07

    @HARIPRASAD Km I don't believe there is a way to achieve 100% without changing the class... first - emailLimit will never be null, so you will not get into that block. There is also no way to set org limits from a test, so the 'close to limit' and 'at limit' branches are not going to hit. I also don't see anyway you would end up in the catch block - nothing in here would fail.

     

    There are some things you can do to refactor that could help, but you'd essentially just be making the changes to get higher coverage, when you could easily get the 75% keeping the class as is. Regardless, if that is your goal, I'll try to help:

     

     The problem is this line:

    Map<String, System.OrgLimit> limits = System.OrgLimits.getMap(); 

     

    The class fetches those numbers itself, so a test can’t get in between. Flip it around - define a small interface with getDailyLimit() and getEmailsUsed(), put the System.OrgLimits call in one class that implements it, and hold that in a @TestVisible private static field on the controller. Production uses the real one by default, and your test reassigns the field to a mock. Your if/else logic doesn’t change at all. 

     

    From there 5000/4995 hits “almost reached”, 5000/5000 hits exceeded, 5000/100 hits the normal path, nulls hit “unable to retrieve”, and a mock whose getter throws hits the catch. Keep one test that doesn’t swap the provider so the real one gets covered too.

     

     

    Here are the classes: 

    public with sharing class SingleEmailLimitController {

    public class EmailLimitResponse {

    @InvocableVariable(label='Daily Single Email Limit')

    public Integer dailyLimit;

    @InvocableVariable(label='Emails Used')

    public Integer emailsUsed;

    @InvocableVariable(label='Emails Remaining')

    public Integer emailsRemaining;

    @InvocableVariable(label='Limit Status Message')

    public String statusMessage;

    }

    public interface LimitProvider {

    Integer getDailyLimit();

    Integer getEmailsUsed();

    }

    public class OrgLimitProvider implements LimitProvider {

    public Integer getDailyLimit() {

    System.OrgLimit l = System.OrgLimits.getMap().get('SingleEmail');

    return l == null ? null : l.getLimit();

    }

    public Integer getEmailsUsed() {

    System.OrgLimit l = System.OrgLimits.getMap().get('SingleEmail');

    return l == null ? null : l.getValue();

    }

    }

    @TestVisible

    private static LimitProvider provider = new OrgLimitProvider();

    @InvocableMethod(

    label='Check Single Email Limit'

    description='Checks daily email usage and returns a status message.'

    )

    public static List<EmailLimitResponse> checkSingleEmailLimit(List<String> requests) {

    List<EmailLimitResponse> results = new List<EmailLimitResponse>();

    if (requests == null || requests.isEmpty()) {

    return results;

    }

    EmailLimitResponse evaluated = evaluate();

    for (Integer i = 0; i < requests.size(); i++) {

    EmailLimitResponse r = new EmailLimitResponse();

    r.dailyLimit = evaluated.dailyLimit;

    r.emailsUsed = evaluated.emailsUsed;

    r.emailsRemaining = evaluated.emailsRemaining;

    r.statusMessage = evaluated.statusMessage;

    results.add(r);

    }

    return results;

    }

    @TestVisible

    private static EmailLimitResponse evaluate() {

    EmailLimitResponse result = new EmailLimitResponse();

    try {

    Integer dailyLimit = provider.getDailyLimit();

    Integer emailsUsed = provider.getEmailsUsed();

    if (dailyLimit == null || emailsUsed == null) {

    result.statusMessage = 'Unable to retrieve the daily email limit.';

    } else {

    result.dailyLimit = dailyLimit;

    result.emailsUsed = emailsUsed;

    result.emailsRemaining = Math.max(0, dailyLimit - emailsUsed);

    if (emailsUsed >= dailyLimit) {

    result.statusMessage =

    'Daily email limit exceeded. No email capacity remains.';

    } else if (result.emailsRemaining <= 10) {

    result.statusMessage =

    'Daily email limit almost reached. Only ' +

    result.emailsRemaining + ' emails remaining.';

    } else {

    result.statusMessage =

    'Email capacity available. ' + result.emailsRemaining +

    ' emails remaining out of ' + dailyLimit + '.';

    }

    }

    } catch (Exception e) {

    result.statusMessage = 'Unable to check email limit: ' + e.getMessage();

    }

    return result;

    }

    }

     

     

    And Test Class

    @IsTest

    private class SingleEmailLimitControllerTest {

    private class MockException extends Exception {}

    private class MockProvider implements SingleEmailLimitController.LimitProvider {

    private Integer dailyLimit;

    private Integer emailsUsed;

    private MockProvider(Integer dailyLimit, Integer emailsUsed) {

    this.dailyLimit = dailyLimit;

    this.emailsUsed = emailsUsed;

    }

    public Integer getDailyLimit() { return dailyLimit; }

    public Integer getEmailsUsed() { return emailsUsed; }

    }

    private class ThrowingProvider implements SingleEmailLimitController.LimitProvider {

    public Integer getDailyLimit() { throw new MockException('simulated failure'); }

    public Integer getEmailsUsed() { return null; }

    }

    private static List<SingleEmailLimitController.EmailLimitResponse> run() {

    return SingleEmailLimitController.checkSingleEmailLimit(new List<String>{ 'go' });

    }

    @IsTest

    static void limitUnavailableReturnsMessage() {

    SingleEmailLimitController.provider = new MockProvider(null, null);

    Test.startTest();

    List<SingleEmailLimitController.EmailLimitResponse> results = run();

    Test.stopTest();

    Assert.areEqual(

    'Unable to retrieve the daily email limit.',

    results[0].statusMessage

    );

    Assert.isNull(results[0].dailyLimit);

    }

    @IsTest

    static void limitExceededReturnsMessage() {

    SingleEmailLimitController.provider = new MockProvider(5000, 5000);

    Test.startTest();

    List<SingleEmailLimitController.EmailLimitResponse> results = run();

    Test.stopTest();

    Assert.areEqual(0, results[0].emailsRemaining);

    Assert.isTrue(results[0].statusMessage.contains('limit exceeded'));

    }

    @IsTest

    static void almostReachedReturnsMessage() {

    SingleEmailLimitController.provider = new MockProvider(5000, 4995);

    Test.startTest();

    List<SingleEmailLimitController.EmailLimitResponse> results = run();

    Test.stopTest();

    Assert.areEqual(5, results[0].emailsRemaining);

    Assert.isTrue(results[0].statusMessage.contains('almost reached'));

    }

    @IsTest

    static void capacityAvailableReturnsMessage() {

    SingleEmailLimitController.provider = new MockProvider(5000, 100);

    Test.startTest();

    List<SingleEmailLimitController.EmailLimitResponse> results = run();

    Test.stopTest();

    Assert.areEqual(4900, results[0].emailsRemaining);

    Assert.isTrue(results[0].statusMessage.contains('Email capacity available'));

    }

    @IsTest

    static void exceptionIsCaught() {

    SingleEmailLimitController.provider = new ThrowingProvider();

    Test.startTest();

    List<SingleEmailLimitController.EmailLimitResponse> results = run();

    Test.stopTest();

    Assert.isTrue(results[0].statusMessage.startsWith('Unable to check email limit:'));

    }

    @IsTest

    static void outputSizeMatchesInputSize() {

    SingleEmailLimitController.provider = new MockProvider(5000, 100);

    Test.startTest();

    List<SingleEmailLimitController.EmailLimitResponse> results =

    SingleEmailLimitController.checkSingleEmailLimit(

    new List<String>{ 'a', 'b', 'c' }

    );

    Test.stopTest();

    Assert.areEqual(3, results.size());

    Assert.areEqual(4900, results[2].emailsRemaining);

    }

    @IsTest

    static void emptyInputReturnsEmptyList() {

    Test.startTest();

    List<SingleEmailLimitController.EmailLimitResponse> results =

    SingleEmailLimitController.checkSingleEmailLimit(new List<String>());

    Test.stopTest();

    Assert.areEqual(0, results.size());

    }

    @IsTest

    static void realProviderReturnsAResult() {

    Test.startTest();

    List<SingleEmailLimitController.EmailLimitResponse> results = run();

    Test.stopTest();

    Assert.areEqual(1, results.size());

    Assert.isNotNull(results[0].statusMessage);

    }

    }

     

     

    Let me know if this all makes sense. Happy to talk through it more

0/9000

What is the best place to begin with a salesforce career? What certificate??

#Trailhead
0/9000

I'm currently working on the "Create Apex Agent Job" challenge as part of the "Agent customization via Apex" 

badge. 

 

Despite having completed all the required steps in my designated org (in two new different orgs requested), I keep running into a verification error. 

 

Here's what I've already checked:

 

  • I'm verifying the challenge against the correct org username: apexlegendbadge@sfdc.comand for the other org : test2@legend.apex
  • I've successfully created and configured both the Apex class and the custom action as instructed
  • The "Einstein Copilot" agent is set up and currently in Active status
  • I've tried clearing my browser cache and logging out and back in — the error still persists
  • I've tried in another browser and I have tried in an incognito window... error still here

 

Has anyone run into this recently? Any suggestions or workarounds would be greatly appreciated. Thanks in advance! 

 

#Trailhead Challenges  #Trailhead

6 réponses
0/9000

#Trailhead #Salesforce Admin #Salesforce DeveloperFor a learner who has completed several Trailhead modules and earned multiple badges, what is the best step-by-step roadmap to strengthen Salesforce Admin and Developer skills and become job-ready?

1 réponse
  1. Forum Ambassador Eric Burté (DEVOTEAM)
    16 oct., 22:04

    Hello @Sai Pavani Yamsani

    , practice, practice, practice. Do not hesitate to apply what you have learned in modules. Follow both certification exam guides for the content to deep dive and train on a playground / personal dev edition to try what you have learned. 

    Eric

0/9000

Dear Community! 

 

The Trailhead community will be shifted to slack in a few weeks. Is there any means of testing the access before the great day?  

 

If so, where and how? 

 

cheers  

 

#Trailhead

1 réponse
  1. Forum Ambassador Eric Burté (DEVOTEAM)
    16 oct., 21:52

    Hello @f ja there is a limited scope beta testing in progress but that is already closed

    • October 30, 2026: Online Trailblazer Community website transitions to read-only.
    • November 2026: General Availability launch of the Trailblazer Community in Slack.

    Please review the FAQ

    for more information 

    Eric

0/9000
Aaron Alexander a posé une question dans #Trailhead Challenges

In the "Connect Flows to Agentforce" challenge within the "Become an Agentforce Innovator" module,  Agentforce is not working at all and an error message is impeding my progress, stating "Something went wrong. Refresh and try again." I've tried restarting the module and refreshing the page a couple of times, but nothing is working 

In Connect Flows to Agentforce challenge, Agentforce is not working

 

and it seems to be in continuous error. 

 

 

 

#Trailhead Challenges  #Trailhead  #Agentforce

1 réponse
0/9000

Dear Community! 

 

Since a couple of weeks I experience a problem when logging into trailhead. After adding the mail sent pin I get forwarded to

https://www.salesforce.com/trailblazer/passkey/enroll

which does not stop loading i.e. is caught in a cycle. 

I can remedy that by going back to the trailhead page and slam "login" again. Than I get forwarded to my "today" page as usual. 

 

Any ideas what causes this problem ... and how to fix it.  

 

Cheers 

 

#Trailhead

1 réponse
  1. 16 oct., 20:58

    Hi , 

     

     I encourage you to get help from the Trailhead support team. 

0/9000

#Trailhead #TrailblazerCommunity #Apex

 

For a learner with strong Trailhead experience who wants to move into Salesforce development, what is the recommended learning sequence for SOQL, Apex, Lightning Web Components (LWC), and REST API integrations, and what hands-on projects can help build confidence in each area?

0/9000

i cannot mark my recorded training pitches as complete in astro 

 

#Trailhead

5 réponses
  1. 16 oct., 13:50

    Vishal, What is the solution to this issue? I have to have these completed by today.

    Neal

0/9000

Good afternoon everyone, how are you? 

 

I have a question regarding the following scenario: I have a client who is about to implement MC Next and wants to proceed with development in a sandbox. 

 

In this case, do we need to complete the full MC Next + Data Cloud setup in production first, and then repeat it in the sandbox? Or, once the setup is done in production, will it correctly mirror to the SANDBOX environment? 

 

Because the scenario I encountered was: the client provided the sandbox environment for us to work in, but while setting up MC Next after activating Data Cloud, I ran into some blockers to proceed with the setup. This raised the question of whether it is necessary to do everything in production first before doing it in the sandbox. 

 

Thank you ! 

 

MC Next - SANDBOX Environment

 

 

 

#Trailhead  #Salesforce Developer  #Salesforce Admin  #Salesforce  #TrailblazerCommunity  #Marketing Cloud

3 réponses
  1. 16 oct., 07:34

    Hi @Guilherme Sequine, here are some of the practices I see most customers adopting: 

    1. SMS: if you have sender codes for Production and Dev use, set up both in Production first, then refresh the sandbox. A code can only be active in one org at a time, so both live in Production but only the real one is active there; in the sandbox, deactivate the real one and activate the test one.
    2. Email: sending domains, From addresses and tracking domains don't copy to sandboxes and can't be deployed between orgs. To send from your own domain, each sandbox needs its own authenticated sending domain; if your sandbox is long lasting and it is relatively easy for you to set a DKIM on your / your customer's domain, a separate subdomain per sandbox keeps it clean. If not, I believe you can activate the "Use a substitute email address for unverified domains" in Setup > Deliverability. It is worth a quick test.
    3. Data 360: provision in Production first, then activate Data 360 in each sandbox separately (if the sandbox exists before Data 360 on Production, activate Data 360 in Production and then refresh the sandbox, or use Match Production Licenses, which can take up to 24h). Connections copy over from Production but come across deactivated, so you need to activate each one. The CRM home connection switches to point at the sandbox org automatically when you activate it.

    There are a lot of helpful videos at this link, including the first one that talks about deployment back to Production: https://help.salesforce.com/s/articleView?id=mktg.mktg_videos.htm&type=5

     

     

    I hope this helps :-)

0/9000