Apex Test class
Apex testing is a crucial aspect of Salesforce development, ensuring the quality and reliability of your code. It involves writing test classes to verify the functionality of your Apex classes and triggers.
Key Concepts:
Test Classes: These are classes annotated with
@isTestthat contain test methods.Test Methods: These are methods within a test class that execute specific scenarios to validate code behavior.
Code Coverage: A metric that measures the percentage of your code executed by test methods. Salesforce requires at least 75% code coverage for deployment.
Writing Effective Test Classes
- Create a Test Class:
@isTest
public class MyTestClass {
@isTest
static void demo() {
}
}
StartTest and StopTest Method
These two controls your governor limit. In your test class you have more than 2 test method and you want separate governor limit for both methods you can wrap you DML statement in these two methods
Test.startTest()
Test.stopTest()
@testsetup annotation
use test setup methods (methods which are annoted with @testsetup) to create test records once and then access them in every test method in the test class.
If test class contains a test setup method, the testing framework method executes the testsetup method first, before any test method in class
→ Records that are created in test setup method are available to all test methods in class and are rolled back at the end of test class execution.
→ If the test method changes those records, such as record field updates or record deletions, those changes are rolled back after each test method finishes execution. The next executing test method gets access to the original unmodified state of those record.
@istest(SeeAlltest=true/false)
@istest(seealltest=true)- Grants full data access: This annotation allows test methods to access all data within the organization, including existing records and data not explicitly created within the test context.
@isTest(SeeAllData=false)(Default)- Restricts data access: Test methods are limited to data created within the test context. This ensures test isolation and prevents unintended side effects.
@TestVisible
- The
@TestVisibleannotation in Apex is a powerful tool that allows you to access private or protected members of a class within your test classes. This is particularly useful when you need to test the internal behavior of a class without exposing its implementation details to the outside world.
public class MyClass {
private Integer privateValue = 10;
@TestVisible
private Integer getPrivateValue() {
return privateValue;
}
}
@isTest
public class MyClassTest {
@isTest
public static void testPrivateValue() {
MyClass myClass = new MyClass();
Integer value = myClass.getPrivateValue();
System.assertEquals(10, value);
}
}
System.runAs() method
Generally, all Apex code runs in system mode, where the permissions and record sharing of the current user aren’t taken into account.
The system method runAs enables you to write test methods that change the user context to an existing user or a new user so that the user’s record sharing is enforced.
The runAs method enforces record sharing.
You can use runAs only in test methods. The original system context is started again after all runAs test methods complete.
The runAs method ignores user license limits. You can create users with runAs even if your organization has no additional user licenses.
Best Practices for apex test classes:
Methods of test class must be static and void
Prepare BULK test data which needs to be used for test runs
System.debug() is not counted as part of apex code coverage
If code used conditional logic then execute each branch
Make calls to method with valid and invalid input
Always try to test both positive and negative scenarios
Skip the unnecessary triggers
Example:
- Test case example for updating total number of opportunity on account
@isTest
public class OpportunityHandlerTest {
@isTest
static void updateTotalNumberOfOpportunitiesAfterInsert() {
Account account = new Account(Name='Test Name');
insert account;
Opportunity opportunity1 = new Opportunity(
Name='Test opp 1',
CloseDate=Date.Today(),
StageName='Prospecting',
AccountId=account.Id,
amount=100
);
Opportunity opportunity2 = new Opportunity(
Name='Test opp 2',
CloseDate=Date.Today(),
StageName='Prospecting',
AccountId = account.Id,
amount=0
);
insert new List<Opportunity>{opportunity1, opportunity2};
// check intial state
List<Account> initiaAccount = [SELECT Id, Total_Opportunities__c FROM Account WHERE Id = :account.Id];
System.assertEquals(0, initiaAccount[0].Total_Opportunities__c, 'Initial');
// update opportunity
opportunity1.StageName = 'Closed Won';
opportunity2.StageName = 'Closed Lost';
update new List<Opportunity>{opportunity1, opportunity2};
List<Account> testAccount = [SELECT Id, Total_Opportunities__c, (SELECT Id FROM Opportunities WHERE StageName = 'Closed Won') FROM Account WHERE Id = :account.Id];
Integer expectedOpportunities = testAccount[0].Opportunities.size();
System.assertEquals(expectedOpportunities, testAccount[0].Total_Opportunities__c, 'Total_Opportunities__c should match the number of Closed Won opportunities.');
}
}
/**
Original code of function
*/
public void updateTotalNumberOfOpportunities(List<Opportunity> opportunityList) {
System.debug('All Opportunities ' + opportunityList);
Set<Id> accountIds = new Set<Id>();
for (Opportunity opportunity : opportunityList) {
accountIds.add(opportunity.AccountId);
}
List<Account> accountList = new List<Account>();
for (Account account : [SELECT Id, (SELECT Id FROM Opportunities WHERE StageName = 'Closed Won') FROM Account WHERE Id IN :accountIds]) {
Account currentAccount = new Account();
currentAccount.Id = account.Id;
currentAccount.Total_Opportunities__c = account.Opportunities.size();
accountList.add(currentAccount);
}
try {
update accountList;
} catch (Exception e) {
System.debug(e.getMessage());
}
}