- Apex Programming Language
- Writing Apex
- Apex Triggers
- Asynchronous Apex
- Debugging Apex
- Testing Apex
- Code Coverage Requirement for Deployment
- What to Test in Apex
- Accessing Private Test Class Members
- Example Test Syntax
- Steps to Apex Testing From Developer Console
- Apex Testing in VS Code
- Create and Execute a Test Suite
- Testing Apex Triggers
- Create Test Data for Apex Tests
- Testing Call Outs (REST/HTTP and SOAP)
- Reference
-
Apex is a
strongly typed
, object-oriented programming language. -
It allows developers to execute
flow and transaction control statements
on the Salesforce Platform server. -
Apex code can only be written in a
sandbox environment or a Developer org
, not in production. -
Apex code can be deployed to a production org from a sandbox.
Trailhead | Build Apex Coding Skills
-
Apex triggers enable you to perform
custom actions before or after events
to records in Salesforce, such asinsertions, updates, or deletions.
-
Just like
database triggers
, Apex provides trigger support for managing records. -
Triggers can be defined for top-level standard objects, such as Account or Contact, custom objects, and some standard child objects.
-
Triggers are active by default when created. Salesforce automatically fires active triggers when the specified database events occur.
-
Typically, you use triggers to perform operations based on specific conditions, to modify related records or restrict certain operations from happening.
trigger TriggerName on ObjectName (trigger_events) {
code_block
}
trigger HelloWorldTrigger on Account (before insert) {
System.debug('Hello World!');
}
-
A trigger definition starts with the
trigger
keyword. -
It is then followed by the
name
of the trigger, the Salesforceobject
that the trigger is associated with, and theconditions
under which it fires.
To execute a trigger before or after insert, update, delete, and undelete operations, specify multiple trigger events in a comma-separated list. The events you can specify are:
- before insert
- before update
- before delete
- after insert
- after update
- after delete
- after undelete
- Setup -> Developer Console -> File | New | Apex Trigger.
- Enter
HelloWorldTrigger
for the trigger name, and then select Account for the sObject. Click Submit.
-
Before triggers are used to
update or validate
record valuesbefore they’re saved
to the database. -
After triggers are used to
access field values
that are set by the system (such as a record's Id or LastModifiedDate field), and to affect changes in other records. The records that fire the after trigger areread-only.
-
To access the records that caused the trigger to fire, use
context variables.
-
Trigger.New
contains all the records that were inserted in insert or update triggers. -
Trigger.Old
provides the old version of sObjects before they were updated in update triggers, or a list of deleted sObjects in delete triggers.
In below example, it iterates over each account in a for loop and updates the Description field for each.
trigger HelloWorldTrigger on Account (before insert) {
for(Account a : Trigger.New) {
a.Description = 'New description';
}
}
The system saves the records that fired the
before trigger
after the trigger finishes execution.You can modify the records in the trigger without explicitly calling a DML insert or update operation.
If you perform DML statements on those records, you get an error.
Another Example showing more than one events.
trigger ContextExampleTrigger on Account (before insert, after insert, after delete) {
}
Trailhead | Find and Fix Bugs with Apex Replay Debugger
-
You can write and execute test cases for your classes and triggers with Apex testing framework on lightning platform.
-
Apex unit tests and code coverage of minimum 75% are requirements for deploying and distributing Apex.
Before each major service upgrade, Salesforce runs all Apex tests on your behalf through a process called
Apex Hammer
. The Hammer process runs in the current version and next release and compares the test results. This process ensures that the behavior in your custom code hasn’t been altered as a result of service upgrades. The Hammer process picks orgs selectively and doesn’t run in all orgs. Issues found are triaged based on certain criteria. Salesforce strives to fix all issues found before each new release.
This metric shows the number of code lines
executed during test case execution, Nothing else!
average = (number of lines executed by test cases)/(total numer of line) * 100%
- Blue lines covered by test cases
- Red lines are not covered by test cases.
Before you can deploy your code or package it for the Salesforce AppExchange, the following must be true.
-
Unit tests must
cover
at least 75% of your Apex code, and all of those tests must complete successfully. -
Every trigger must have some test coverage.
-
All classes and triggers must compile successfully.
- Single Actions
- Test to verify that a single record produces the correct, expected result.
- Bulk Actions
- Any Apex code, whether a trigger, a class or an extension, may be invoked for 1 to 200 records.
- You must test not only the single record case, but the bulk cases as well.
- Positive Behavior
- Test to verify that the expected behavior occurs through every expected permutation, that is, that the user filled out everything correctly and did not go past the limits.
- Negative Behavior
- There are likely limits to your applications, such as not being able to add a future date, not being able to specify a negative amount, and so on.
- You must test for the negative case and verify that the error messages are correctly produced as well as for the positive, within the limits cases.
- Restricted User
- Test whether a user with restricted access to the sObjects used in your code sees the expected behavior.
- That is, whether they can run the code or receive error messages.
If you want to test private/protected members (methods, variables, classes)
, you have 2 options
- Make a public method which uses private members.
- Annotate private/protected members of a class with
@TestVisible
so that you can access them from your test class and use those members.
- Test
classes
can be either private or public. - If you’re using a test class for unit testing only, declare it as
private
. - Public test classes are typically used for test data factory classes.
- Test
methods
must be defined in test classes, which are classes annotated withisTest
. - The
visibility
of a test method doesn’t matter, so declaring a test method as public or private doesn’t make a difference as the testing framework is always able to access test methods. - For this reason, the access modifiers are omitted in the syntax.
- Declare method as
static
.
@isTest
private class MyTestClass {
@isTest static void myTest() {
System.assertEquals('Expected Value',
'Actual Value',
'Failed Message');
}
}
- The verifications are done by calling the
System.assertEquals()
method, which takes two parameters: the first is the expected value, and the second is the actual value. - There is another version of this method that takes a third parameter—a string that describes the comparison being done.
- This optional string is logged if the assertion fails.
- In the Developer Console, click
File -> New -> Apex Class
, and enter the class name e.g;TestTemperatureConverter
for the class name, and then click OK. - Annotate class and it's methods with
@isTest
- In the Developer Console, click
Test -> New Run
- Under Test Classes, select your class, e.g;
TestTemperatureConverter
. - To add all the test methods in the
TestTemperatureConverter
class to the test run, click Add Selected. - Click Run.
- In the
Tests tab
, you see the status of your tests as they’re running. Expand the test run, and expand again until you see the list of individual tests that were run. They all have green checkmarks.
After you run tests, code coverage is automatically generated for the Apex classes and triggers in the org. You can check the code coverage percentage in the Tests tab of the Developer Console.
While one test method would have resulted in full coverage of the class in question, it’s still important to test for different inputs to ensure the quality of your code.
- Triggers are tested by firing the trigger and verify expected results.
- To isolate the data setup process’s limit usage, enclose the test call within the Test.startTest() and Test.stopTest() block
- The
TestDataFactory
class is a special type of class—it is a public class that is annotated with isTest and can be accessed only from a running test. - Test utility classes contain methods that can be called by test methods to perform useful tasks, such as setting up test data.
- Test utility classes are excluded from the org’s code size limit.
- By default, test methods don’t support SOAP and REST callouts, and tests that perform web service callouts fail.
-
For
SOAP
, Apex provides the built-inWebServiceMock
interface and theTest.setMock
method. -
Use
WebServiceMock
andTest.setMock
toreceive fake responses
in a test method. -
WSDL2Apex auto-generated class call
WebServiceCallout.invoke
, which performs the callout to the external service. -
When testing these methods, you can instruct the Apex runtime to
generate a fake response
wheneverWebServiceCallout.invoke
is called. -
To do so, implement the
WebServiceMock interface
and specify afake response
for the Apex runtime to send.
- First, implement the
WebServiceMock
interface and specify the fake response in thedoInvoke
method. - The class implementing the WebServiceMock interface can be either
global or public.
- You can annotate this class with @isTest because it is used only in a test context. In this way, you can exclude it from your org’s code size limit of 6 MB.
@isTest
global class CalculatorCalloutMock implements WebServiceMock {
global void doInvoke(
Object stub,
Object request,
Map<String, Object> response,
String endpoint,
String soapAction,
String requestName,
String responseNS,
String responseName,
String responseType) {
// start - specify the response you want to send
calculatorServices.doAddResponse response_x =
new calculatorServices.doAddResponse();
response_x.return_x = 3.0;
// end
response.put('response_x', response_x);
}
}
- Call the service.
public class AwesomeCalculator {
public static Double add(Double x, Double y) {
calculatorServices.CalculatorImplPort calculator =
new calculatorServices.CalculatorImplPort();
return calculator.doAdd(x,y);
}
}
-
Now that you have specified the values of the fake response, instruct the Apex runtime to send this fake response by calling
Test.setMock
in your test method. -
For the first argument, pass
WebServiceMock.class
, and for the second argument, pass a new instance of your interface implementation ofWebServiceMock
. -
After this point, if a web service callout is invoked in test context, the callout is not made. You receive the mock response specified in your doInvoke method implementation.
@isTest
private class AwesomeCalculatorTest {
@isTest static void testCallout() {
// This causes a fake response to be generated
Test.setMock(WebServiceMock.class, new CalculatorCalloutMock());
// Call the method that invokes a callout
Double x = 1.0;
Double y = 2.0;
Double result = AwesomeCalculator.add(x, y);
// Verify that a fake result is returned
System.assertEquals(3.0, result);
}
}
- For
REST
, Apex provides the built-inHttpCalloutMock
interface to specify the response sent in the respond method, which the Apex runtime calls to send a response for a callout.
-
First, implement the
HttpCalloutMock
interface and specify the fake response in theresponse
method. -
The class implementing the
HttpCalloutMock
interface can be eitherglobal or public.
-
You can annotate this class with @isTest because it is used only in a test context. In this way, you can exclude it from your org’s code size limit of 6 MB.
@isTest
global class AnimalsHttpCalloutMock implements HttpCalloutMock {
// Implement this interface method
global HTTPResponse respond(HTTPRequest request) {
// Create a fake response
HttpResponse response = new HttpResponse();
response.setHeader('Content-Type', 'application/json');
response.setBody('{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}');
response.setStatusCode(200);
return response;
}
}
- Call the method which have REST callout and provide the mock response before executing the method
@isTest
static void testPostCallout() {
// Set mock callout class
Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock());
// This causes a fake response to be sent
// from the class that implements HttpCalloutMock.
HttpResponse response = AnimalsCallouts.makePostCallout();
// Verify that the response received contains fake values
String contentType = response.getHeader('Content-Type');
System.assert(contentType == 'application/json');
String actualValue = response.getBody();
System.debug(response.getBody());
String expectedValue = '{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}';
System.assertEquals(expectedValue, actualValue);
System.assertEquals(200, response.getStatusCode());
}
-
It's possible that you have to make multiple callouts in one test method. To provide mock response, following code will do the trick.
-
And even if you don't have multiple requests in one test method, below code is good approach to provide response in one file for multiple use cases.
private class Mock implements HttpCalloutMock {
public HTTPResponse respond(HTTPRequest req) {
if (req.getEndpoint().endsWith('abc')) {
HTTPResponse res = new HTTPResponse();
res.setBody('{}');
res.setStatusCode(200);
return res;
} else if (req.getEndpoint().endsWith('xyz')) {
...
} else {
System.assert(false, 'unexpected endpoint ' + req.getEndpoint());
return null;
}
}
}
And you can use it like any other mock.
@IsTest
static void abc() {
Test.setMock(HttpCalloutMock.class, new Mock());
...
}
@IsTest
static void xyz() {
Test.setMock(HttpCalloutMock.class, new Mock());
...
}