function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Dave SpraggDave Spragg 

stuck on Trailhead "Asynchronous Apex - Using Future Methods" test coverage

Admittedly I have lame tests but I can't get the coverage to include the code in my loop and have tried several ways. The auto indentation is messed up in my test class.. might be a hint but I don't get it.  When I check the challenge it returns:
Challenge Not yet complete... here's what's wrong: 
The 'AccountProcessor' class did not achieve 100% code coverage via your test methods. Make sure that you chose 'Run All' tests in the Developer Console at least once before attempting to verify this challenge.

global class AccountProcessor {

    @future
    global static void countContacts(List<Id> recordIds)
    {
        //List<Account> accounts = [Select Id, Name from Account Where Id IN :recordIds];
        for(Account theAccount : [Select Id, Name from Account Where Id IN :recordIds])
        {
            Integer contactCount =0;
            contactCount= [Select COUNT() FROM Contact WHERE Contact.AccountId = :theAccount.Id];
            System.debug('Account/Count:'+ theAccount.Name +' '+contactCount);
            
            theAccount.Number_of_Contacts__c= contactCount;
            update theAccount;
        }
    }
}

@IsTest
private class AccountProcessorTest
{
    @IsTest
    private static void testAccountProcessor()
    {
        List<Id> listOfIds= new List<Id>{'0011500001DH89EAAT','0011500001DH89GAAT','0011500001DH89FAAT'};
        Test.startTest();
            try
            {
                AccountProcessor.countContacts(listOfIds);
            }
        catch(Exception e)
        {
        }
        Test.stopTest();
    }
}
Mahesh DMahesh D
Hi Dave,

Please find the below Test Class:
 
@isTest
private class AccountProcessorTest {
    @isTest
    static void testMethodOne() {
        Account acc = new Account();
        acc.Name='Test Account' ;
        insert acc;

        Contact cont = new Contact();
        cont.FirstName='Test';
        cont.LastName='Test';
        cont.Email='demo@demo.com';
        cont.Accountid=acc.id;
        insert cont;
        
        Test.startTest();
        
        List<Id> accList = new List<Id>();
        accList.add(acc.Id);
        AccountProcessor.countContacts(accList);
        Test.stopTest();
        Account accNew = [select Id, Number_of_Contacts__c from Account where Id =: acc.Id LIMIT 1];
        
        System.assertEquals(1, accNew.Number_of_Contacts__c);
        
    }
}

Please follow below salesforce Best Practice for Test Classes:

1. Test class must start with @isTest annotation if class class version is more than 25
2. Test environment support @testVisible , @testSetUp as well
3. Unit test is to test particular piece of code working properly or not .
4. Unit test method takes no argument ,commit no data to database ,send no email ,flagged with testMethod keyword .
5. To deploy to production at-least 75% code coverage is required
6. System.debug statement are not counted as a part of apex code limit.
7. Test method and test classes are not counted as a part of code limit
9. We should not focus on the  percentage of code coverage ,we should make sure that every use case should covered including positive, negative,bulk and single record .
Single Action -To verify that the the single record produces the correct an expected result .
Bulk action -Any apex record trigger ,class or extension must be invoked for 1-200 records .
Positive behavior : Test every expected behavior occurs through every expected permutation , i,e user filled out every correctly data and not go past the limit .
Negative Testcase :-Not to add future date , Not to specify negative amount.
Restricted User :-Test whether a user with restricted access used in your code .
10. Test class should be annotated with @isTest .
11 . @isTest annotation with test method  is equivalent to testMethod keyword .
12. Test method should static and no void return type .
13. Test class and method default access is private ,no matter to add access specifier .
14. classes with @isTest annotation can't be a interface or enum .
15. Test method code can't be invoked by non test request .
16. Stating with salesforce API 28.0 test method can not reside inside non test classes .
17. @Testvisible annotation to make visible private methods inside test classes.
18. Test method can not be used to test web-service call out . Please use call out mock .
19. You can't  send email from test method.
20.User, profile, organization, AsyncApexjob, Corntrigger, RecordType, ApexClass, ApexComponent ,ApexPage we can access without (seeAllData=true) .
21. SeeAllData=true will not work for API 23 version eailer .
22. Accessing static resource test records in test class e,g List<Account> accList=Test.loadData(Account,SobjectType,'ResourceName').
23. Create TestFactory class with @isTest annotation to exclude from organization code size limit .
24. @testSetup to create test records once in a method  and use in every test method in the test class .
25. We can run unit test by using Salesforce Standard UI,Force.com IDE ,Console ,API.
26. Maximum number of test classes run per 24 hour of period is  not grater of 500 or 10 multiplication of test classes of your organization.
27. As apex runs in system mode so the permission and record sharing are not taken into account . So we need to use system.runAs to enforce record sharing .
28. System.runAs will not enforce user permission or field level permission .
29. Every test to runAs count against the total number of DML issued in the process.

Please do let me know if it helps you.

Regards,
Mahesh
Mahesh DMahesh D
Hi Dave,

Testing Future Methods

To test methods defined with the future annotation, call the class containing the method in a startTest(), stopTest()code block. All asynchronous calls made after the startTest method are collected by the system. When stopTest is executed, all asynchronous processes are run synchronously.

Also go through the below link which will be helpful for you.

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_invoking_future_methods.htm

Regards,
Mahesh

 
Amit Chaudhary 8Amit Chaudhary 8
Hi Dave,

I found below issue in your code
1) Query inside the for loop.
2) DML inside for loop
3) Hard code ID in TEst class.
4) Assert is missing in your test class.

Please update your code like below :-

Apex class
public class AccountProcessor 
{
  @future
  public static void countContacts(Set<id> setId) 
  {
      List<Account> lstAccount = [select id,Number_of_Contacts__c , (select id from contacts ) from account where id in :setId ];
      for( Account acc : lstAccount )
      {
          List<Contact> lstCont = acc.contacts ;
          
          acc.Number_of_Contacts__c = lstCont.size();
      }
      update lstAccount;
  }
}

Test class:-
@IsTest
public class AccountProcessorTest {
    public static testmethod void TestAccountProcessorTest() 
    {
        Account a = new Account();
        a.Name = 'Test Account';
        Insert a;

        Contact cont = New Contact();
        cont.FirstName ='Bob';
        cont.LastName ='Masters';
        cont.AccountId = a.Id;
        Insert cont;
        
        set<Id> setAccId = new Set<ID>();
        setAccId.add(a.id);
 
        Test.startTest();
            AccountProcessor.countContacts(setAccId);
        Test.stopTest();
        
        Account ACC = [select Number_of_Contacts__c from Account where id = :a.id LIMIT 1];
        System.assertEquals ( Integer.valueOf(ACC.Number_of_Contacts__c) ,1);
  }
  
}


Some best pratice for apex class. I hope that will hhelp you

1) One Trigger Per Object
A single Apex Trigger is all you need for one particular object. If you develop multiple Triggers for a single object, you have no way of controlling the order of execution if those Triggers can run in the same contexts

2) Logic-less Triggers
If you write methods in your Triggers, those can’t be exposed for test purposes. You also can’t expose logic to be re-used anywhere else in your org.

3) Context-Specific Handler Methods
Create context-specific handler methods in Trigger handlers

4) Bulkify your Code
Bulkifying Apex code refers to the concept of making sure the code properly handles more than one record at a time.

5) Avoid SOQL Queries or DML statements inside FOR Loops
An individual Apex request gets a maximum of 100 SOQL queries before exceeding that governor limit. So if this trigger is invoked by a batch of more than 100 Account records, the governor limit will throw a runtime exception

6) Using Collections, Streamlining Queries, and Efficient For Loops
It is important to use Apex Collections to efficiently query data and store the data in memory. A combination of using collections and streamlining SOQL queries can substantially help writing efficient Apex code and avoid governor limits

7) Querying Large Data Sets
The total number of records that can be returned by SOQL queries in a request is 50,000. If returning a large set of queries causes you to exceed your heap limit, then a SOQL query for loop must be used instead. It can process multiple batches of records through the use of internal calls to query and queryMore

8) Use @future Appropriately
It is critical to write your Apex code to efficiently handle bulk or many records at a time. This is also true for asynchronous Apex methods (those annotated with the @future keyword). The differences between synchronous and asynchronous Apex can be found

9) Avoid Hardcoding IDs
When deploying Apex code between sandbox and production environments, or installing Force.com AppExchange packages, it is essential to avoid hardcoding IDs in the Apex code. By doing so, if the record IDs change between environments, the logic can dynamically identify the proper data to operate against and not fail

http://amitsalesforce.blogspot.in/2015/06/trigger-best-practices-sample-trigger.html

Let us know if this will help you

Thanks
Amit Chaudhary
Bill PhillipsBill Phillips

Amit,

I have a question about your APEX code. I don't think it is necessary to reference the Contacts subquery on line six.

Alexey Solonenko 1Alexey Solonenko 1
Can't share my 50cents.

Special thanks to Amit for point (9) 

Perhaps, I'm wrong, but it seems to me, that unless I changed hard-coded IDs to a dynamically generated ones, test platform only rated 83% coverage. 

Nevertheless, I truly enjoyed this task - an excellent exercise for apex beginners. My version below.

 
@isTest 
    public static void testAccountProcessorCount() {
        List<String> testAccIds = new List<String>();
        testAccIds = new List<String>();
        for(Integer j = 0; j < 10; j++){
             Account a = new Account();
        	a.Name = 'Test test';
        	insert a;
        
        	Contact c = new Contact();
        	c.FirstName = 'Test FN';
        	c.LastName = 'Test LN';
        	c.AccountId = a.Id;
        	insert c;
            testAccIds.add(a.Id);
        }
       
        Map<ID, Account> accsBefore = new Map<ID, Account>([select Id, (SELECT Id from Contacts) from Account where Id in :testAccIds]);
        
        Test.startTest();
        AccountProcessor.countContacts(testAccIds);
        Test.stopTest();
        Map<ID, Account> accsAfter = new Map<ID, Account>([select Id, Number_of_Contacts__c, (SELECT Id from Contacts) from Account where Id in :testAccIds]);
        
        for(Account a: accsAfter.values()){
        	System.assert(a.Number_of_Contacts__c == accsBefore.get(a.Id).Contacts.size());
        }
        
    }

 
Nikhil Muppidi 2Nikhil Muppidi 2
Future apex method

public class AccountProcessor {
    @future
    public static void countContacts(List<id> accountIds){
        //(A1=>(c1,c2),A2=>(c3,c4,c5),A3=>(c6,c7,c8))
        //Map<id,List<contact>> accContactMap = new Map<id,List<contact>>();
        List<Contact> conList = [SELECT id, AccountId from Contact WHERE AccountId IN: accountIds];
        Set<Id> accIds = new Set<Id>();//[A1,A2,A3]
        for(Contact myCon: conList){
            accIds.add(myCon.AccountId);
        }
        
        //Get the accounts which has contacts
        List<Account> accList = [SELECT Number_Of_Contacts__c,(select Id from Contacts) from Account WHERE Id IN: accIds];//[A1,A2,A3]
        for(Account myAcc: accList){
            system.debug(myAcc.Contacts.size());
            myAcc.Number_Of_Contacts__c = myAcc.Contacts.size();//[2,3,3]
        }
        update accList;
    }
}

TEST CLASS

@isTest
public class AccountProcessorTest {
    @isTest public static void testMethod1(){
        List<Account> accsToInsert = new List<Account>();
        Account acc1 = new Account(name='acc1');
        Account acc2 = new Account(name='acc2');
        accsToInsert.add(acc1);
        accsToInsert.add(acc2);
        insert accsToInsert;
        
        List<Contact> conList = new List<Contact>{new Contact(lastname='con1',AccountId=acc1.Id),
                                                  new Contact(lastname='con2',AccountId=acc1.Id),
                                                  new Contact(lastname='con3',AccountId=acc2.Id),
                                                  new Contact(lastname='con4',AccountId=acc2.Id)};
        insert conList;
        //List of AccountIds
        //List<id> accIds = new List<id>();
        Set<id> setaccIds = new Map<id,Account>([SELECT ID from Account WHERE ID in: accsToInsert]).keySet();
        List<id> accIds = new List<id>(setaccIds);
        Test.startTest();
        AccountProcessor.countContacts(accIds);
        Test.stopTest();
        //Get the latest account records
        List<Account> latestAccs = [SELECT Number_Of_Contacts__c from Account WHERE ID IN: accIds ];
        system.assertEquals(2, latestAccs[0].Number_Of_Contacts__c);
        system.assertEquals(2, latestAccs[1].Number_Of_Contacts__c);
        
    }
        
}

Hope This helps