You are on page 1of 72

Data Type Description and Example

Integer A positive or negative number that doesn’t have a decimal point.

Integer num = 12;

Decimal A positive or negative number that has a decimal point.

Decimal num = 12.22222;

String A series of characters surrounded by single quotes. This can include any text as sh

String whatAmI = 'A String';

Boolean Typically either true or false. In Apex, null (empty) is also a valid value. Boolean is c
checkboxes.

Boolean doSomething = False;

ID (Salesforce Any valid 18-character Salesforce record ID.


ID)
Id contactId = '00300000003T2PGAA0';

Collections
A collection is a type of variable that can store multiple items. In our tea example, we
have a sugar bowl with several sugar cubes inside it. The sugar bowl is considered a
collection and the sugar cubes are items that are stored in that collection.

Although there are three types of Apex collections (lists, sets, and maps), in this
module we focus on lists.  
Lists
An Apex list is an ordered group of items of the same type, like a shopping list. 

1. Tea
2. Sugar
3. Honey
4. Milk

Each item in the shopping list is a string, and each item has a position within the list.
To declare a list you need a few things. You will use the list reserved word, the data
type (all items must be the same data type) within the <> characters, and the
reserved word new. At the end of this statement there are required parentheses. 

Note
There are different ways to declare lists in Apex. This example uses array notation in
order to set the list size.

Declare a List with a Set Size


If we know exactly how long our list needs to be, we can set the size when we declare
the list. We do this by including the size in brackets, [ ], after the data type, like this: 

Initialize a List
Initializing a list is assigning initial values to a list. There are two ways to add items to
a list:
1. Declare and initialize a list.
2. //Sets the first item in the list to 'Tea'

List <String> groceries = new List<String>{'Tea'};

Copy

3. Declare an empty list and add values later.


4. List<String> groceries = new List<String>();

groceries .add('Tea');

Declare an Empty List and Add Values Later

//Create the groceries list

List<String> groceries = new List<String>();

//The output for this statement is null

System.debug('Groceries: ' + groceries);

//Use the add method to add an element to the list

groceries.add('Tea');

groceries.add('Sugar');

groceries.add('Honey');

groceries.add(2, 'Milk');

//The output for this statement is Tea, Sugar, Milk, Honey

System.debug('Groceries: ' + groceries);

Line 11 demonstrates another way to add an item to a list, by inserting the item in a
specific index (position): 
Demystifying Lists
Different ways of adding items to a list produce slightly different results; sometimes
unexpected results. Try this exercise.

1. In the Developer Console, click Debug | Open Execute Anonymous Window.


2. Copy this code and paste it into the Enter Apex Code window.
3. String[] groceries = new String[4];

4. System.debug('Groceries: ' + groceries);

5.

6. groceries.add('Tea');

7. groceries.add('Sugar');

8. System.debug('Added 2 items: ' + groceries);

9.

10. groceries.add(1, 'Honey');

11. System.debug('Added Honey in index 1: ' + groceries);

12.

System .debug('Item 0: ' + groceries[0]);

Copy

13. Select the Open log checkbox and click Execute. The Execution Log opens,
displaying the result of running your code.
14. Select the Debug Only checkbox at the bottom of the window.

Interesting results:
When we added the two strings in lines 4 and 5, we didn’t specify an index, so Tea
and Sugar were added onto the end of the list, increasing the list size from four
strings to six strings. In line 8, we explicitly specified index 1 for Honey, so why isn’t
Honey first in the list? Remember when programming, we always start counting with
0 instead of 1. In line 11, the first index in the list is 0, which currently contains
a  null  value. 

Expressions and Expression Operators


Before we make decisions, we often make comparisons. Is this larger than that? Is
this equal to that? Developers often compare values before making decisions in their
applications. In code, we compare two expressions by placing an expression operator
between them. 

An expression is a snippet of code that, when evaluated, results in a single value. For
our purposes in this module, an expression might be a literal value (such as 12), or a
variable (such as numberOfTeaCups), or a mathematical operation (such as 7 x 4).

Expression operators compare or show equality between two values. A comparison


statement consists of two expressions separated by an expression operator, and
produces a Boolean value, either true or false. 

Comparison Operators

Operator Description Syntax

< Less than 1<2

<= Less than or equal to 1 <= 2


3 <= 3

== Equal to 10 == 10

!= Not equal to 10 != 0
<> 10 <> 11

> Greater than 11 > 10

>= Greater than or equal to 40 >=10


40 >= 40

One equals sign (  =  ) is an assignment operator. It assigns values. Two equals signs (  ==  ) is
a comparison operator. It compares two values to determine whether they are equal (true) or
not equal (false).

Conditional Statements

If-Else Statements

A commonly used conditional statement in Apex is the  if-else  statement. It looks


like this:

if (condition is true) {

//do this

} else {

//do this

If-Else If Statements

To handle more than two possibilities, we use an if-else if statement. The if-else if
statement adds another if condition before the final else condition. Let’s look at an
example in action.

1. String waterLevel = 'half';

2.

3. if(waterLevel == 'empty') {

4. System.debug('Fill the tea kettle');

5. waterLevel = 'full';

6. } else if(waterLevel == 'half') {

7. System.debug('Fill the tea kettle');

8. waterLevel = 'full';
9. } else { /*This statement only runs if line 2 and line 5 result in
false.*/

10. System.debug('The tea kettle is full');

Switch Statements

A more efficient alternative to an if-else statement is a switch statement. A switch


statement specifies a set of values and tests an expression to determine whether it
matches one of those values. Here is how it looks:

switch on expression {

when value1 {

//code block 1

when value2 {

//code block 2

when else { //if none of the previous values apply

//code block 3

In a switch statement, you can have one or more values after the  when  reserved
word. 

switch on expression {

when value1 { //single value

//code block 1

when value2 , value3 { //multiple values

//code block 2

}
}

EXAMPLE

String waterLevel = 'empty';

//option 1 using a single value

switch on waterLevel {

when 'empty' {

System.debug('Fill the tea kettle');

when 'half' {

System.debug('Fill the tea kettle');

when 'full' {

System.debug('The tea kettle is full');

when else {

System.debug('Error!');

//option 2 using multiple values

switch on waterLevel {

when 'empty', 'half' { //when waterLevel is either empty


or half

System.debug('Fill the tea kettle');

}
when 'full' {

System.debug('The tea kettle is full');

when else {

System.debug('Error!');

Use Loops
A loop is a block of code that is repeated until a certain condition is met. In the case
of the Teatime code, let’s review the steps to be repeated

The differentiator is when they verify that the condition is met.  While  loops check the
condition before the loop starts, and  do-while  loops check it after the loop is
finished. 

Seems like such a small detail, right? Actually, the impact is pretty big. 

Think of it this way: the  do-while  loop always runs at least once. The  while  loop
might never run, depending on the condition. 

What’s an sObject?
An sObject is an Apex data type that corresponds to a Salesforce object (sObject) in
an org. sObjects are complex data types that hold multiple values in one variable.
They hold a single record of data from a Salesforce object, such as an Account, a
Contact, or an Opportunity. Remember from Apex Basics for Admins
that variables are like containers. Most variables hold one piece of information.
sObjects are containers that hold other containers. The containers within the sObject
container may be of different data types, such as string, date, integer or Boolean. 

Because resources are limited, you can use the insert statement in your code only 150 times.
One record at a time, you can insert a total of 150 records. But if each insert statement
inserts 10 records, that total increases to 1,500 records. Inserting multiple objects at once
helps with bulkification (combining repetitive tasks by writing efficient code). At first you may
be inserting as few as 10 records at a time. But as your application grows, you might begin to
insert 200 records at a time. Using bulkification from the start prepares your application for
growth.

EXAMPLE

public class AccountHandler {

public static void insertAccount(Integer N)

list<Account>addAccounts=new list<Account>();

Integer counter=1;

while(counter<=N)

Account acc=new Account();

acc.Name='Acme Inc '+N;

acc.AccountNumber='A000'+1;

addAccounts.add(acc);

counter = counter + 1;

insert addAccounts;

Sets
Until now, you’ve created one type of collection, a list. A set is an unordered set of
unique items of the same type. Similar to a list, a set is a group of items, called
elements, and all elements have the same data type, such as string, integer, or even
Account. Unlike a list, a set maintains no particular order for its elements. Because
the elements are unordered, a set can't have any duplicates. If you try to add an
element that’s already in the set, you don’t get an error, but the new value is not
added to the set. Remember that when you loop through a list, you always access its
items in the order that the items were added to the list. When you loop through a
set, because the elements are unordered, you may access elements in a random
order.

Declaring a set is like declaring a list, but you substitute the keyword  Set  for  List .
Let’s create a set of strings.

Remember that sets don’t allow repeated values. Also, keep in mind you can’t index a set like
you can with a List. Sets are typically used for checking if a certain value is included.

Maps
A map is a more complex collection than either a list or a set. Each item in a map has
two parts: a key and a value, known as a key-value pair. Keys and values can be any
data type. Although each key is unique, values can be repeated within a map.
Imagine a map of telephone country codes. The country code is the key and the
name of the country is the value. Each country code is unique, but the countries can
be duplicated (because a country may have more than one country code). 

The Put Method

To add key-value pairs to a map, use the  put  method, like this:

The  put  method expects two parameters: key and value. Let’s create a map of tea types
(keys) and their flavor profiles (values). 

The Get Method

To access values in a map, use the  get  method, like this: 

When provided with an existing key, the  get  method returns the value of the key. If
the provided key is not mapped to a value, the  get  method returns null. Remember
that the map declaration specifies the data type it expects for a return value. The
variable that returns a value must be the same data type that the map declaration
specifies.
Collection Type Description
List An ordered group of items of the same type.
Each item has an index number that represents its position in the list.
Set An unordered group of unique items of the same type.
Map A collection of key-value pairs. Each unique key maps to a single
value.

List or Set Iteration For Loops


The list or set iteration  for  loop (iteration  for  loop) is a variation of the
traditional  for  loop. The iteration  for  loop, works through the items in a list or set.
Because the list or set has a specific number of items, you don’t need to increment a
variable or check a condition. The loop works through all of the items in the list or
set, and then the loop ends.

The syntax for an iteration  for  loop is a little different than the traditional  for  loop
syntax. When you declare an iteration  for  loop, the data type of the variable must
match the data type of the list or set. Here’s the syntax for an iteration for loop.

for (data_type variable_name : list_name or set_name){

// Loop body

Copy

Write an Iteration For Loop

Let’s create an iteration  for  loop to work through this list.

List <String> tea = new List<String>{'Black Tea', 'Green Tea', 'Chai Tea'};

Note that the  tea  list has the string data type. To declare the iteration  for  loop, we
use the list data type (string) and the list name (tea).

for (String t : tea)  

Data Types Overview


Apex supports various data types, including a data type specific to Salesforce—the sObject
data type.

Apex supports the following data types.

 A primitive, such as an Integer, Double, Long, Date, Datetime, String, ID,


Boolean, among others.

 An sObject, either as a generic sObject or as a specific sObject, such as an


Account, Contact, or MyCustomObject__c (you’ll learn more about sObjects in
a later unit.)

 A collection, including:

o A list (or array) of primitives, sObjects, user defined objects, objects


created from Apex classes, or collections

o A set of primitives

o A map from a primitive to a primitive, sObject, or collection

 A typed list of values, also known as an enum

 User-defined Apex classes

 System-supplied Apex classes

Apex Collections: List

Lists hold an ordered collection of objects. Lists in Apex are synonymous with arrays
and the two can be used interchangeably.

The following two declarations are equivalent. The  colors  variable is declared using
the List syntax.

List <String> colors = new List<String>();

Copy

Alternatively, the  colors  variable can be declared as an array but assigned to a list
rather than an array.

String [] colors = new List<String>();


Apex Classes
One of the benefits of Apex classes is code reuse. Class methods can be called by triggers
and other classes. The following tutorial walks you through saving an example class in your
organization, using this class to send emails, and inspecting debug logs.

Save an Apex Class

Save the  EmailManager  class in your organization:

1. Open the Developer Console under Your Name or the quick access menu ( ).

2. In the Developer Console, click File | New | Apex Class, and


enter EmailManager for the class name, and then click OK.

3. Replace the default class body with the  EmailManager  class example.

The  EmailManager  class has a public method ( sendMail() ) that sends email and
uses built-in Messaging methods of the Apex class library. Also, this class has
a private helper method ( inspectResults() ), which can’t be called externally
because it is private but is used only within the class. This helper method
inspects the results of the email send call and is called by  sendMail() .

public class EmailManager {

// Public method

public void sendMail(String address, String subject, String


body) {

// Create an email message object

Messaging.SingleEmailMessage mail = new


Messaging.SingleEmailMessage();

String[] toAddresses = new String[] {address};

mail .setToAddresses(toAddresses);

mail .setSubject(subject);

mail .setPlainTextBody(body);

// Pass this email message to the built-in sendEmail method

// of the Messaging class


Messaging.SendEmailResult[] results = Messaging.sendEmail(

new Messaging.SingleEmailMessage[]
{ mail });

// Call a helper method to inspect the returned results

inspectResults(results);

// Helper method

private static Boolean


inspectResults(Messaging.SendEmailResult[] results) {

Boolean sendResult = true;

// sendEmail returns an array of result objects.

// Iterate through the list to inspect results.

// In this class, the methods send only one email,

// so we should have only one result.

for (Messaging.SendEmailResult res : results) {

if (res.isSuccess()) {

System.debug('Email sent successfully');

else {

sendResult = false;

System.debug('The following errors occurred: ' +


res.getErrors());

}
return sendResult;

Copy

4. Click Ctrl+S to save your class.

Call a Method to Send an Email

Let’s invoke the public method. We’ll use anonymous Apex execution to do so.
Anonymous Apex allows you to run lines of code on the fly and is a handy way to
invoke Apex, especially to test out functionality. Debug log results are generated, as
with any other Apex execution.

Note
There are other ways to invoke Apex, for example, through triggers. You’ll learn more
about triggers in another module.

1. In the Developer Console, click Debug | Open Execute Anonymous Window.

2. In the window that opens, enter the following. Replace 'Your email


address' with your email address.

3. EmailManager em = new EmailManager();

4. em.sendMail('Your email address', 'Trailhead Tutorial', '123 body');

Copy

5. Click Execute.

Now that this method has executed, you should have received an email in
your inbox. Check your email!

Use sObjects
Because Apex is tightly integrated with the database, you can access Salesforce records and
their fields directly from Apex. Every record in Salesforce is natively represented as
an sObject in Apex. For example, the Acme account record corresponds to an Account
sObject in Apex. The fields of the Acme record that you can view and modify in the user
interface can be read and modified directly on the sObject as well.

The following table lists some populated fields of the Acme account example record.
The Account sObject is an abstraction of the account record and holds the account
field information in memory as an object.

Table 1. Account sObject for a Retrieved Record

Account Field Value

Id 001D000000JlfXe

Name Acme

Phone (415)555-1212

NumberOfEmployees 100

Each Salesforce record is represented as an sObject before it is inserted into


Salesforce. Likewise, when persisted records are retrieved from Salesforce, they’re
stored in an sObject variable.

Standard and custom object records in Salesforce map to their sObject types in Apex.
Here are some common sObject type names in Apex used for standard objects.

 Account

 Contact

 Lead

 Opportunity

If you’ve added custom objects in your organization, use the API names of the
custom objects in Apex. For example, a custom object called Merchandise
corresponds to the Merchandise__c sObject in Apex.

Creating sObjects and Adding Fields

Before you can insert a Salesforce record, you must create it in memory first as an
sObject. Like with any other object, sObjects are created with the  new  operator:
Account acct = new Account();

Copy

The API object name becomes the data type of the sObject variable in Apex. In this
example,  Account  is the data type of the  acct  variable.

The account referenced by the  acct  variable is empty because we haven’t populated
any of its fields yet. There are two ways to add fields: through the constructor or by
using dot notation.

The fastest way to add fields is to specify them as name-value pairs inside the
constructor. For example, this statement creates a new account sObject and
populates its Name field with a string value.

Account acct = new Account(Name='Acme');

Copy

The Name field is the only required field for accounts, which means that it has to be
populated before being able to insert a new record. However, you can populate
other fields as well for the new record. This example adds also a phone number and
the number of employees.

Account acct = new Account(Name='Acme', Phone='(415)555-1212',


NumberOfEmployees=100);

Copy

Alternatively, you can use the dot notation to add fields to an sObject. The following
is equivalent to the previous example, although it takes a few more lines of code.

Account acct = new Account();

acct .Name = 'Acme';

acct .Phone = '(415)555-1212';

acct .NumberOfEmployees = 100;

Working with the Generic sObject Data Type


Typically, you use the specific sObject data type, such as Account for a standard object or
Book__c for a custom object called Book, when working with sObjects. However, when you
don’t know the type of sObject your method is handling, you can use the generic sObject
data type.

Variables that are declared with the generic sObject data type can reference any
Salesforce record, whether it is a standard or custom object record.

This example shows how the generic sObject variable can be assigned to any
Salesforce object: an account and a custom object called Book__c.

sObject sobj1 = new Account(Name='Trailhead');

sObject sobj2 = new Book__c(Name='Workbook 1');

Copy

In contrast, variables that are declared with the specific sObject data type can
reference only the Salesforce records of the same type.

Casting Generic sObjects to Specific sObject Types

When you’re dealing with generic sObjects, you sometimes need to cast your sObject
variable to a specific sObject type. One of the benefits of doing so is to be able to
access fields using dot notation, which is not available on the generic sObject. Since
sObject is a parent type for all specific sObject types, you can cast a generic sObject
to a specific sObject. This example shows how to cast a generic sObject to Account.

// Cast a generic sObject to an Account

Account acct = (Account)myGenericSObject;

// Now, you can use the dot notation to access fields on Account

String name = acct.Name;

String phone = acct.Phone;

DML Statements

The following DML statements are available.

 insert

 update

 upsert

 delete

 undelete

 merge

Each DML statement accepts either a single sObject or a list (or array) of sObjects.
Operating on a list of sObjects is a more efficient way for processing records.

All those statements, except a couple, are familiar database operations.


The  upsert  and  merge  statements are particular to Salesforce and can be quite handy.

The  upsert  DML operation creates new records and updates sObject records within a
single statement, using a specified field to determine the presence of existing
objects, or the ID field if no field is specified.

The  merge  statement merges up to three records of the same sObject type into one
of the records, deleting the others, and re-parenting any related records.
ID Field Auto-Assigned to New Records

When inserting records, the system assigns an ID for each record. In addition to
persisting the ID value in the database, the ID value is also autopopulated on the
sObject variable that you used as an argument in the DML call.

This example shows how to get the ID on the sObject that corresponds to the
inserted account.

// Create the account sObject

Account acct = new Account(Name='Acme', Phone='(415)555-1212',


NumberOfEmployees=100);

// Insert the account by using DML

insert acct ;

// Get the new ID on the inserted sObject argument

ID acctID = acct.Id;

// Display this ID in the debug log

System .debug('ID = ' + acctID);

// Debug log result (the ID will be different in your case)

// DEBUG|ID = 001D000000JmKkeIAF

You can retrieve a record from the database to obtain its fields, including the ID field, but
this can’t be done with DML. You’ll need to write a query by using SOQL. You’ll learn about
SOQL in another unit.

Bulk DML

You can perform DML operations either on a single sObject, or in bulk on a list of
sObjects. Performing bulk DML operations is the recommended way because it helps
avoid hitting governor limits, such as the DML limit of 150 statements per Apex
transaction. This limit is in place to ensure fair access to shared resources in the
Lightning Platform. Performing a DML operation on a list of sObjects counts as one
DML statement, not as one statement for each sObject.

This example inserts contacts in bulk by inserting a list of contacts in one call. The
sample then updates those contacts in bulk too.
1. Execute this snippet in the Developer Console using Anonymous Apex.

2. // Create a list of contacts

3. List<Contact> conList = new List<Contact> {

4. new
Contact(FirstName='Joe',LastName='Smith',Department='Finance'),

5. new
Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),

6. new
Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),

7. new
Contact(FirstName='Kim',LastName='Shain',Department='Education')};

8.

9. // Bulk insert all contacts with one DML call

10. insert conList;

11. // List to hold the new contacts to update

12. List<Contact> listToUpdate = new List<Contact>();

13. // Iterate through the list and add a title only

14. // if the department is Finance

15. for(Contact con : conList) {

16. if (con.Department == 'Finance') {

17. con.Title = 'Financial analyst';

18. // Add updated contact sObject to the list.

19. listToUpdate.add(con);

20. }

21. }

22. // Bulk update all contacts with one DML call

23. update listToUpdate;


Copy

24. Inspect the contacts recently created in your org.

Two of the contacts who are in the Finance department should have their
titles populated with Financial analyst.

Upserting Records

If you have a list containing a mix of new and existing records, you can process
insertions and updates to all records in the list by using the  upsert  statement. Upsert
helps avoid the creation of duplicate records and can save you time as you don’t
have to determine which records exist first.

The  upsert  statement matches the sObjects with existing records by comparing
values of one field. If you don’t specify a field when calling this statement,
the  upsert  statement uses the sObject’s ID to match the sObject with existing
records in Salesforce. Alternatively, you can specify a field to use for matching. For
custom objects, specify a custom field marked as external ID. For standard objects,
you can specify any field that has the idLookup property set to true. For example, the
Email field of Contact or User has the idLookup property set. To check a field’s
property, see the Object Reference for Salesforce and Lightning Platform.

Upsert Syntax

upsert   sObject | sObject[]

upsert   sObject | sObject[] field

The optional field is a field token. For example, to specify the MyExternalID field, the
statement is:

upsert sObjectList Account.Fields.MyExternalId;

Copy

Upsert uses the sObject record's primary key (the ID), an idLookup field, or an
external ID field to determine whether it should create a new record or update an
existing one:

 If the key is not matched, a new object record is created.

 If the key is matched once, the existing object record is updated.


 If the key is matched multiple times, an error is generated and the object
record is neither inserted or updated.

This example shows how upsert updates an existing contact record and inserts a new
contact in one call. This upsert call updates the existing Josh contact and inserts a
new contact, Kathy.

Note
The upsert call uses the ID to match the first contact. The  josh  variable is being
reused for the upsert call. This variable has already been populated with the record
ID from the previous insert call, so the ID doesn’t need to be set explicitly in this
example.

1. Execute this snippet in the Execute Anonymous window of the Developer


Console.

2. // Insert the Josh contact

3. Contact josh = new


Contact(FirstName='Josh',LastName='Kaplan',Department='Finance');

4. insert josh;

5. // Josh's record has been inserted

6. // so the variable josh has now an ID

7. // which will be used to match the records by upsert

8. josh.Description = 'Josh\'s record has been updated by the upsert


operation.';

9. // Create the Kathy contact, but don't persist it in the database

10. Contact kathy = new


Contact(FirstName='Kathy',LastName='Brown',Department='Technology');

11. // List to hold the new contacts to upsert

12. List<Contact> contacts = new List<Contact> { josh, kathy };

13. // Call upsert

14. upsert contacts;


// Result: Josh is updated and Kathy is created.

Copy

15. Inspect all contacts in your org.

Your org will have only one Josh Kaplan record, not two, because the upsert
operation found the existing record and updated it instead of creating a new
contact record. One Kathy Brown contact record will be there too.

Alternatively, you can specify a field to be used for matching records. This example
uses the Email field on Contact because it has idLookup property set. The example
inserts the Jane Smith contact, and creates a second Contact sObject, populates it
with the same email, then calls  upsert  to update the contact by using the email field
for matching.

Alternatively, you can specify a field to be used for matching records. This example
uses the Email field on Contact because it has idLookup property set. The example
inserts the Jane Smith contact, and creates a second Contact sObject, populates it
with the same email, then calls  upsert  to update the contact by using the email field
for matching.

Note
If  insert  was used in this example instead of  upsert , a duplicate Jane Smith contact
would have been inserted.

1. Execute this snippet in the Execute Anonymous window of the Developer


Console.

2. Contact jane = new Contact(FirstName='Jane',

3. LastName='Smith',

4. Email='jane.smith@example.com',

5. Description='Contact of the day');

6. insert jane;

7. // 1. Upsert using an idLookup field

8. // Create a second sObject variable.


9. // This variable doesn’t have any ID set.

10. Contact jane2 = new Contact(FirstName='Jane',

11. LastName='Smith',

12. Email='jane.smith@example.com',

13. Description='Prefers to be contacted by


email.');

14. // Upsert the contact by using the idLookup field for matching.

15. upsert jane2 Contact.fields.Email;

16. // Verify that the contact has been updated

17. System.assertEquals('Prefers to be contacted by email.',

18. [SELECT Description FROM Contact WHERE


Id=:jane.Id].Description);

Copy

19. Inspect all contacts in your org.

Your org will have only one Jane Smith contact with the updated description.

Database Methods
Apex contains the built-in Database class, which provides methods that perform DML
operations and mirror the DML statement counterparts.

These Database methods are static and are called on the class name.

 Database.insert()

 Database.update()

 Database.upsert()

 Database.delete()

 Database.undelete()
 Database.merge()

Unlike DML statements, Database methods have an optional allOrNone parameter


that allows you to specify whether the operation should partially succeed. When this
parameter is set to  false , if errors occur on a partial set of records, the successful
records will be committed and errors will be returned for the failed records. Also, no
exceptions are thrown with the partial success option.

This is how you call the  insert  method with the allOrNone set to  false .

Database .insert(recordList, false);

Copy

The Database methods return result objects containing success or failure information
for each record. For example, insert and update operations each return an array
of  Database.SaveResult  objects.

Database .SaveResult[] results = Database.insert(recordList, false);

Copy

Note
Upsert returns  Database.UpsertResult  objects, and delete
returns  Database.DeleteResult  objects.

By default, the allOrNone parameter is  true , which means that the Database method
behaves like its DML statement counterpart and will throw an exception if a failure is
encountered.

The following two statements are equivalent to the  insert recordList;  statement.

Database .insert(recordList);

Copy

And:

Database .insert(recordList, true);

Copy
Beyond the Basics
In addition to these methods, the Database class contains methods that aren’t
provided as DML statements. For example, methods used for transaction control and
rollback, for emptying the Recycle Bin, and methods related to SOQL queries. You’ll
learn about SOQL in another unit.
Example: Inserting Records with Partial Success

Let’s take a look at an example that uses the Database methods. This example is
based on the bulk DML example, but replaces the DML statement with a Database
method. The  Database.insert()  method is called with the partial success option. One
contact in the list doesn’t have any fields on purpose and will cause an error because
the contact can’t be saved without the required LastName field. Three contacts are
committed and the contact without any fields generates an error. The last part of this
example iterates through the returned results and writes debug messages to the
debug log.

1. Execute this example in the Execute Anonymous window of the Developer


Console.

2. // Create a list of contacts

3. List<Contact> conList = new List<Contact> {

4. new
Contact(FirstName='Joe',LastName='Smith',Department='Finance'),

5. new
Contact(FirstName='Kathy',LastName='Smith',Department='Technology'),

6. new
Contact(FirstName='Caroline',LastName='Roth',Department='Finance'),

7. new Contact()};

8.

9. // Bulk insert all contacts with one DML call

10. Database.SaveResult[] srList = Database.insert(conList, false);

11. // Iterate through each returned result

12. for (Database.SaveResult sr : srList) {

13. if (sr.isSuccess()) {
14. // Operation was successful, so get the ID of the record
that was processed

15. System.debug('Successfully inserted contact. Contact ID: '


+ sr.getId());

16. } else {

17. // Operation failed, so get all errors

18. for(Database.Error err : sr.getErrors()) {

19. System.debug('The following error has occurred.');

20. System.debug(err.getStatusCode() + ': ' +


err.getMessage());

21. System.debug('Contact fields that affected this error:


' + err.getFields());

22. }

23. }

24. }

Copy

25. Verify the debug messages (use the DEBUG keyword for the filter).

One failure should be reported and three contacts should have been inserted.

Should You Use DML Statements or Database Methods?

 Use DML statements if you want any error that occurs during bulk DML
processing to be thrown as an Apex exception that immediately interrupts
control flow (by using  try. . .catch  blocks). This behavior is similar to the way
exceptions are handled in most database procedural languages.

 Use Database class methods if you want to allow partial success of a bulk DML
operation—if a record fails, the remainder of the DML operation can still
succeed. Your application can then inspect the rejected records and possibly
retry the operation. When using this form, you can write code that never
throws DML exception errors. Instead, your code can use the appropriate
results array to judge success or failure. Note that Database methods also
include a syntax that supports thrown exceptions, similar to DML statements.
Inserting Related Records

You can insert records related to existing records if a relationship has already been
defined between the two objects, such as a lookup or master-detail relationship. A
record is associated with a related record through a foreign key ID. For example, if
inserting a new contact, you can specify the contact's related account record by
setting the value of the  AccountId  field.

This example shows how to add a contact to an account (the related record) by
setting the  AccountId  field on the contact. Contact and Account are linked through a
lookup relationship.

1. Execute this snippet in the Anonymous Apex window of the Developer


Console.

2. Account acct = new Account(Name='SFDC Account');

3. insert acct;

4. // Once the account is inserted, the sObject will be

5. // populated with an ID.

6. // Get this ID.

7. ID acctID = acct.ID;

8. // Add a contact to this account.

9. Contact mario = new Contact(

10. FirstName='Mario',

11. LastName='Ruiz',

12. Phone='415.555.1212',

13. AccountId=acctID);

14. insert mario;

Copy

15. Inspect the contacts in your org.

A new account (SFDC Account) has been created and has the Mario Ruiz
contact in the account’s Contacts related list.
Updating Related Records
Fields on related records can't be updated with the same call to the DML operation and
require a separate DML call. For example, if inserting a new contact, you can specify the
contact's related account record by setting the value of the  AccountId  field. However, you
can't change the account's name without updating the account itself with a separate DML
call. Similarly, when updating a contact, if you also want to update the contact’s related
account, you must make two DML calls. The following example updates a contact and its
related account using two  update  statements.

// Query for the contact, which has been associated with an account.

Contact queriedContact = [SELECT Account.Name

FROM Contact

WHERE FirstName = 'Mario' AND LastName='Ruiz'

LIMIT 1];

// Update the contact's phone number

queriedContact .Phone = '(415)555-1213';

// Update the related account industry

queriedContact .Account.Industry = 'Technology';

// Make two separate calls

// 1. This call is to update the contact's phone.

update queriedContact ;

// 2. This call is to update the related account's Industry field.

update queriedContact.Account;

Copy

Deleting Related Records

The  delete  operation supports cascading deletions. If you delete a parent object, you
delete its children automatically, as long as each child record can be deleted.

For example, deleting the account you created earlier (SFDC Account) will delete its
related contact too.
1. Execute this snippet in the Anonymous Apex window of the Developer
Console.

2. Account[] queriedAccounts = [SELECT Id FROM Account WHERE Name='SFDC


Account'];

delete queriedAccounts ;

Copy

3. Check the accounts and contacts in your org.

You’ll see that both the account and its related contact were deleted.

Beyond the Basics

Unlike other SQL languages, you can’t specify * for all fields. You must specify every
field you want to get explicitly. If you try to access a field you haven’t specified in the
SELECT clause, you’ll get an error because the field hasn’t been retrieved.

You don’t need to specify the Id field in the query as it is always returned in Apex
queries, whether it is specified in the query or not. For example:  SELECT Id,Phone FROM
Account  and  SELECT Phone FROM Account  are equivalent statements. The only time you

may want to specify the Id field if it is the only field you’re retrieving because you
have to list at least one field:  SELECT Id FROM Account . You may want to specify the Id
field also when running a query in the Query Editor as the ID field won’t be displayed
unless specified.
Filtering Query Results with Conditions

If you have more than one account in the org, they will all be returned. If you want to
limit the accounts returned to accounts that fulfill a certain condition, you can add
this condition inside the WHERE clause. The following example retrieves only the
accounts whose names are SFDC Computing. Note that comparisons on strings are
case-insensitive.

SELECT Name,Phone FROM Account WHERE Name='SFDC Computing'

Copy

The WHERE clause can contain multiple conditions that are grouped by using logical
operators (AND, OR) and parentheses. For example, this query returns all accounts
whose name is SFDC Computing that have more than 25 employees:

SELECT Name,Phone FROM Account WHERE (Name='SFDC Computing' AND


NumberOfEmployees>25)
Accessing Variables in SOQL Queries

SOQL statements in Apex can reference Apex code variables and expressions if they
are preceded by a colon (:). The use of a local variable within a SOQL statement is
called a bind.

This example shows how to use the  targetDepartment  variable in the WHERE clause.

String targetDepartment = 'Wingo';

Contact [] techContacts = [SELECT FirstName,LastName

FROM Contact WHERE


Department=:targetDepartment];

Querying Related Records

Records in Salesforce can be linked to each other through relationships: lookup


relationships or master-detail relationships. For example, the Contact has a lookup
relationship to Account. When you create or update a contact, you can associate it
with an account. The contacts that are associated with the same account appear in a
related list on the account’s page. In the same way you can view related records in
the Salesforce user interface, you can query related records in SOQL.

To get child records related to a parent record, add an inner query for the child
records. The FROM clause of the inner query runs against the relationship name,
rather than a Salesforce object name. This example contains an inner query to get all
contacts that are associated with each returned account. The FROM clause specifies
the Contacts relationship, which is a default relationship on Account that links
accounts and contacts.

SELECT Name, (SELECT LastName FROM Contacts) FROM Account WHERE Name =
'SFDC Computing'

Copy

This next example embeds the example SOQL query in Apex and shows how to get
the child records from the SOQL result by using the Contacts relationship name on
the sObject.

Account [] acctsWithContacts = [SELECT Name, (SELECT FirstName,LastName


FROM Contacts)

FROM Account
WHERE Name = 'SFDC Computing'];

// Get child records

Contact [] cts = acctsWithContacts[0].Contacts;

System .debug('Name of first associated contact: '

+ cts[0].FirstName + ', ' + cts[0].LastName);

Copy

You can traverse a relationship from a child object (contact) to a field on its parent
(Account.Name) by using dot notation. For example, the following Apex snippet
queries contact records whose first name is Carol and is able to retrieve the name of
Carol’s associated account by traversing the relationship between accounts and
contacts.

Contact [] cts = [SELECT Account.Name FROM Contact

WHERE FirstName = 'Carol' AND LastName='Ruiz'];

Contact carol = cts[0];

String acctName = carol.Account.Name;

System .debug('Carol\'s account name is ' + acctName);

Copy

Note
The examples in this section are based on standard objects. Custom objects can also
be linked together by using custom relationships. Custom relationship names end
with the __r suffix. For example, invoice statements are linked to line items through
the Line_Items__r relationship on the Invoice_Statement__c custom object.
Querying Record in Batches By Using SOQL For Loops

With a SOQL for loop, you can include a SOQL query within a  for  loop. The results of
a SOQL query can be iterated over within the loop. SOQL for loops use a different
method for retrieving records—records are retrieved using efficient chunking with
calls to the query and queryMore methods of the SOAP API. By using SOQL for
loops, you can avoid hitting the heap size limit.
SOQL  for  loops iterate over all of the sObject records returned by a SOQL query. The syntax
of a SOQL  for  loop is either:

for (variable : [soql_query]) {

code_block

Copy

or

for (variable_list : [soql_query]) {

code_block

Copy

Both  variable  and  variable_list  must be of the same type as the sObjects that are
returned by the  soql_query .

It is preferable to use the sObject list format of the SOQL for loop as the loop
executes once for each batch of 200 sObjects. Doing so enables you to work on
batches of records and perform DML operations in batch, which helps avoid reaching
governor limits.

insert new Account[]{new Account(Name = 'for loop 1'),

new Account(Name = 'for loop 2'),

new Account(Name = 'for loop 3')};

// The sObject list format executes the for loop once per returned batch

// of records

Integer i=0;

Integer j=0;

for (Account[] tmp : [SELECT Id FROM Account WHERE Name LIKE 'for loop
_']) {

j = tmp.size();
i ++;

System .assertEquals(3, j); // The list should have contained the three
accounts

// named 'yyy'

System .assertEquals(1, i); // Since a single batch can hold up to 200


records and,

// only three records should have been returned,


the

// loop should have executed only once

Write SOSL Queries


Salesforce Object Search Language (SOSL) is a Salesforce search language that is used to
perform text searches in records. Use SOSL to search fields across multiple standard and
custom object records in Salesforce. SOSL is similar to Apache Lucene.

Adding SOSL queries to Apex is simple—you can embed SOSL queries directly in
your Apex code. When SOSL is embedded in Apex, it is referred to as inline SOSL.

This is an example of a SOSL query that searches for accounts and contacts that have
any fields with the word 'SFDC'.

List <List<SObject>> searchList = [FIND 'SFDC' IN ALL FIELDS

RETURNING Account(Name),
Contact(FirstName,LastName)];

Copy

Differences and Similarities Between SOQL and SOSL

Like SOQL, SOSL allows you to search your organization’s records for specific
information. Unlike SOQL, which can only query one standard or custom object at a
time, a single SOSL query can search all objects.

Another difference is that SOSL matches fields based on a word match while SOQL
performs an exact match by default (when not using wildcards). For example,
searching for 'Digital' in SOSL returns records whose field values are 'Digital' or 'The
Digital Company', but SOQL returns only records with field values of 'Digital'.

SOQL and SOSL are two separate languages with different syntax. Each language has
a distinct use case:

 Use SOQL to retrieve records for a single object.

 Use SOSL to search fields across multiple objects. SOSL queries can search
most text fields on an object.

Prerequisites

Some queries in this unit expect the org to have accounts and contacts. If you
haven’t created the sample data in the SOQL unit, create sample data in this unit.
Otherwise, you can skip creating the sample data in this section.

1. In the Developer Console, open the Execute Anonymous window from


the Debug menu.

2. Insert the below snippet in the window and click Execute.

// Add account and related contact

Account acct = new Account(

Name='SFDC Computing',

Phone='(415)555-1212',

NumberOfEmployees=50,

BillingCity='San Francisco');

insert acct ;

// Once the account is inserted, the sObject will be

// populated with an ID.

// Get this ID.

ID acctID = acct.ID;

// Add a contact to this account.

Contact con = new Contact(


FirstName='Carol',

LastName='Ruiz',

Phone='(415)555-1212',

Department='Wingo',

AccountId=acctID);

insert con ;

// Add account with no contact

Account acct2 = new Account(

Name='The SFDC Query Man',

Phone='(310)555-1213',

NumberOfEmployees=50,

BillingCity='Los Angeles',

Description='Expert in wing technologies.');

insert acct2 ;

Copy

Using the Query Editor

The Developer Console provides the Query Editor console, which enables you to run
SOSL queries and view results. The Query Editor provides a quick way to inspect the
database. It is a good way to test your SOSL queries before adding them to your
Apex code. When you use the Query Editor, you need to supply only the SOSL
statement without the Apex code that surrounds it.

Let’s try running the following SOSL example:

1. In the Developer Console, click the Query Editor  tab.

2. Copy and paste the following into the first box under Query Editor, and then
click Execute.

FIND {Wingo} IN ALL FIELDS RETURNING Account(Name),


Contact(FirstName,LastName,Department)
Copy

All account and contact records in your org that satisfy the criteria will display in the
Query Results section as rows with fields. The results are grouped in tabs for each
object (account or contact). The SOSL query returns records that have fields whose
values match Wingo. Based on our sample data, only one contact has a field with the
value Wingo, so this contact is returned..

Note
The search query in the Query Editor and the API must be enclosed within curly
brackets ( {Wingo} ). In contrast, in Apex the search query is enclosed within single
quotes ( 'Wingo' ).
Basic SOSL Syntax
SOSL allows you to specify the following search criteria:

 Text expression (single word or a phrase) to search for

 Scope of fields to search

 List of objects and fields to retrieve

 Conditions for selecting rows in the source objects

This is the syntax of a basic SOSL query:

FIND 'SearchQuery' [IN SearchGroup] [RETURNING ObjectsAndFields]

Copy

SearchQuery is the text to search for (a single word or a phrase). Search terms can be
grouped with logical operators (AND, OR) and parentheses. Also, search terms can
include wildcard characters (*, ?). The * wildcard matches zero or more characters at
the middle or end of the search term. The ? wildcard matches only one character at
the middle or end of the search term.

Text searches are case-insensitive. For example, searching for  Customer ,  customer ,
or  CUSTOMER  all return the same results.

SearchGroup is optional. It is the scope of the fields to search. If not specified, the
default search scope is all fields. SearchGroup can take one of the following values.
 ALL FIELDS

 NAME FIELDS

 EMAIL FIELDS

 PHONE FIELDS

 SIDEBAR FIELDS

ObjectsAndFields is optional. It is the information to return in the search result—a list


of one or more sObjects and, within each sObject, list of one or more fields, with
optional values to filter against. If not specified, the search results contain the IDs of
all objects found.

Single Words and Phrases

A SearchQuery contains two types of text:

 Single Word— single word, such as  test  or  hello . Words in
the  SearchQuery  are delimited by spaces, punctuation, and changes from
letters to digits (and vice-versa). Words are always case insensitive.

 Phrase— collection of words and spaces surrounded by double quotes such


as  "john smith" . Multiple words can be combined together with logic and
grouping operators to form a more complex query.

Search Examples

To learn about how SOSL search works, let’s play with different search strings and
see what the output is based on our sample data. This table lists various example
search strings and the SOSL search results.

Search in all
Search Description Matched Re
fields for:

The Query This search returns all records whose fields contain both words: The and Query, in any Account: Th
location of the text. The order of words in the search term doesn’t matter. matched)

Wingo OR This search uses the OR logical operator. It returns records with fields containing the word Contact: Ca
Man Wingo or records with fields containing the word Man.
Account: T
field match
Search in all
Search Description Matched Re
fields for:

1212 This search returns all records whose fields contain the word 1212. Phone fields that end Account: Th
with -1212 are matched because 1212 is considered a word when delimited by the dash. '(415)555-1

Contact: C
1212'

wing* This is a wildcard search. This search returns all records that have a field value starting Contact: M
with wing.
Account: T
Descriptio

SOSL Apex Example

This example shows how to run a SOSL query in Apex. This SOSL query combines two
search terms by using the OR logical operator—it searches for Wingo or SFDC in any
field. This example returns all the sample accounts because they each have a field
containing one of the words. The SOSL search results are returned in a list of lists.
Each list contains an array of the returned records. In this case, the list has two
elements. At index 0, the list contains the array of accounts. At index 1, the list
contains the array of contacts.

Execute this snippet in the Execute Anonymous window of the Developer Console.
Next, inspect the debug log to verify that all records are returned.

List <List<sObject>> searchList = [FIND 'Wingo OR SFDC' IN ALL FIELDS

RETURNING
Account(Name),Contact(FirstName,LastName,Department)];

Account [] searchAccounts = (Account[])searchList[0];

Contact [] searchContacts = (Contact[])searchList[1];

System .debug('Found the following accounts.');

for (Account a : searchAccounts) {

System.debug(a.Name);

System .debug('Found the following contacts.');

for (Contact c : searchContacts) {


System.debug(c.LastName + ', ' + c.FirstName);

Types of Triggers

There are two types of triggers.

 Before triggers are used to update or validate record values before 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 are read-only.

Using Context Variables

To access the records that caused the trigger to fire, use context variables. For
example,  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. Triggers can fire
when one record is inserted, or when many records are inserted in bulk via the API or
Apex. Therefore, context variables, such as  Trigger.New , can contain only one record
or multiple records. You can iterate over  Trigger.New  to get each individual sObject.

This example is a modified version of the  HelloWorldTrigger  example trigger. 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';

Copy
Note
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.
Trigger Context Variables

The following table is a comprehensive list of all context variables available for
triggers.

Variable Usage

isExecuting Returns true if the current context for the Apex code is a trigger, not a Visualforce page, a

Web service, or an  executeanonymous()  API call.

isInsert Returns  true  if this trigger was fired due to an insert operation, from the Salesforce user interface

Apex, or the API.

isUpdate Returns  true  if this trigger was fired due to an update operation, from the Salesforce user interfa

Apex, or the API.

isDelete Returns  true  if this trigger was fired due to a delete operation, from the Salesforce user interface

Apex, or the API.

isBefore Returns  true  if this trigger was fired before any record was saved.

isAfter Returns  true  if this trigger was fired after all records were saved.

isUndelete Returns  true  if this trigger was fired after a record is recovered from the Recycle Bin.

This recovery can occur after an undelete operation from the Salesforce user interface, Apex, or

the API.

New Returns a list of the new versions of the sObject records.

This sObject list is only available in  insert ,  update , and  undelete  triggers, and the

records can only be modified in  before  triggers.

newMap A map of IDs to the new versions of the sObject records.

This map is only available in  before update ,  after insert ,  after update , and  after undel

Old Returns a list of the old versions of the sObject records.


Variable Usage

This sObject list is only available in  update  and  delete  triggers.

oldMap A map of IDs to the old versions of the sObject records.

This map is only available in  update  and  delete  triggers.

operationType Returns an enum of type System.TriggerOperation corresponding to the current operation.

Possible values of the System.TriggerOperation enum are:  BEFORE_INSERT , 

BEFORE_UPDATE ,  BEFORE_DELETE , AFTER_INSERT , 

AFTER_UPDATE ,  AFTER_DELETE , and  AFTER_UNDELETE .

If you vary your programming logic based on different trigger types,

consider using the  switch  statement with different permutations of unique trigger

execution enum states.

Size The total number of records in a trigger invocation, both old and new.

Calling a Class Method from a Trigger

You can call public utility methods from a trigger. Calling methods of other classes
enables code reuse, reduces the size of your triggers, and improves maintenance of
your Apex code. It also allows you to use object-oriented programming.

The following example trigger shows how to call a static method from a trigger. If the
trigger was fired because of an insert event, the example calls the
static  sendMail()  method on the  EmailManager  class. This utility method sends an email
to the specified recipient and contains the number of contact records inserted.

Note
The  EmailManager  class is included in the class example of the Get Started with Apex
unit. You must have saved the  EmailManager  class in your org and changed
the  sendMail()  method to static before saving this trigger.
1. In the Developer Console, click File | New | Apex Trigger.

2. Enter ExampleTrigger for the trigger name, and then select Contact for the


sObject. Click Submit.

3. Replace the default code with the following, and then modify the email
address placeholder text in  sendMail()  to your email address.

4. trigger ExampleTrigger on Contact (after insert, after delete) {

5. if (Trigger.isInsert) {

6. Integer recordCount = Trigger.New.size();

7. // Call a utility method from another class

8. EmailManager.sendMail('Your email address', 'Trailhead


Trigger Tutorial',

9. recordCount + ' contact(s) were inserted.');

10. }

11. else if (Trigger.isDelete) {

12. // Process after delete

13. }

Copy

14. To save, press Ctrl+S.

15. To test the trigger, create a contact.

a. Click Debug | Open Execute Anonymous Window.

b. In the new window, add the following and then click Execute.

c. Contact c = new Contact(LastName='Test Contact');

insert c ;

Copy
16. In the debug log, check that the trigger was fired. Toward the end of the log,
find the debug message that was written by the utility method:  DEBUG|Email
sent successfully

17. Now check that you received an email with the body text  1 contact(s) were
inserted.

Bulk Design Pattern in Action: Trigger Example for Getting


Related Records

Let’s apply the design patterns you’ve learned by writing a trigger that accesses
accounts’ related opportunities. Modify the trigger example from the previous unit
for the  AddRelatedRecord  trigger. The  AddRelatedRecord  trigger operates in bulk, but is
not as efficient as it could be because it iterates over all  Trigger.New  sObject records.
This next example modifies the SOQL query to get only the records of interest and
then iterate over those records. If you haven’t created this trigger, don’t worry—you
can create it in this section.

Let’s start with the requirements for the  AddRelatedRecord  trigger. The trigger fires
after accounts are inserted or updated. The trigger adds a default opportunity for
every account that doesn’t already have an opportunity. The first problem to tackle is
to figure out how to get the child opportunity records. Because this trigger is
an after trigger, we can query the affected records from the database. They’ve
already been committed by the time the after trigger is fired. Let’s write a SOQL
query that returns all accounts in this trigger that don’t have related opportunities.

[ SELECT Id,Name FROM Account WHERE Id IN :Trigger.New AND

Id NOT IN (SELECT AccountId


FROM Opportunity)]

Copy

Now that we got the subset of records we’re interested in, let’s iterate over those
records by using a SOQL for loop, as follows.

for (Account a : [SELECT Id,Name FROM Account WHERE Id IN :Trigger.New AND

Id NOT IN (SELECT AccountId FROM


Opportunity)]){

Copy
You’ve now seen the basics of our trigger. The only missing piece is the creation of
the default opportunity, which we’re going to do in bulk. Here’s the complete trigger.

1. If you’ve already created the AddRelatedRecord trigger in the previous unit,


modify the trigger by replacing its contents with the following trigger.
Otherwise, add the following trigger using the Developer Console and
enter AddRelatedRecord for the trigger name.

2. trigger AddRelatedRecord on Account(after insert, after update) {

3. List<Opportunity> oppList = new List<Opportunity>();

4.

5. // Add an opportunity for each account if it doesn't already


have one.

6. // Iterate over accounts that are in this trigger but that don't
have opportunities.

7. for (Account a : [SELECT Id,Name FROM Account

8. WHERE Id IN :Trigger.New AND

9. Id NOT IN (SELECT AccountId FROM Opportunity)])


{

10. // Add a default opportunity for this account

11. oppList.add(new Opportunity(Name=a.Name + ' Opportunity',

12. StageName='Prospecting',

13.
CloseDate=System.today().addMonths(1),

14. AccountId=a.Id));

15. }

16.

17. if (oppList.size() > 0) {

18. insert oppList;

19. }

}
Copy

20. To test the trigger, create an account in the Salesforce user interface and
name it Lions & Cats.

21. In the Opportunities related list on the account’s page, find the new
opportunity Lions & Cats. The trigger added the opportunity automatically!

Asynchronous Apex
In a nutshell, asynchronous Apex is used to run processes in a separate thread, at a
later time.

An asynchronous process is a process or function that executes a task "in the


background" without the user having to wait for the task to finish.

User efficiency

Let's say you have a process that makes many calculations on a custom object
whenever an Opportunity is created. The time needed to execute these calculations
could range from a minor annoyance to a productivity blocker for the user. Since
these calculations don't affect what the user is currently doing, making them wait for
a long running process is not an efficient use of their time. With asynchronous
processing the user can get on with their work, the processing can be done in the
background and the user can see the results at their convenience.

Scalability

By allowing some features of the platform to execute when resources become


available at some point in the future, resources can be managed and scaled quickly.
This allows the platform to handle more jobs using parallel processing.

Higher Limits

Asynchronous processes are started in a new thread, with higher governor and
execution limits. And to be honest, doesn’t everyone want higher governor and
execution limits?

Asynchronous Apex comes in a number of different flavors. We’ll get into more detail
for each one shortly, but here’s a high level overview.

Type Overview Common Scenarios


Type Overview Common Scenarios
Batch Apex Run large jobs that would exceed normal processing limits. Data cleansing
or archiving of
records.
Queueable Similar to future methods, but provide additional job chaining and allow more Performing
Apex complex data types to be used. Sequential
processing operations
Web services.
Scheduled Schedule Apex to run at a specified time. Daily or weekly tasks.
Apex

Increased Governor and Execution Limits


One of the main benefits of running asynchronous Apex is higher governor and
execution limits. For example, the number of SOQL queries is doubled from 100 to
200 queries when using asynchronous calls. The total heap size and maximum CPU
time are similarly larger for asynchronous calls.

Future Method Syntax


Future methods must be static methods, and can only return a void type. The
specified parameters must be primitive data types, arrays of primitive data types, or
collections of primitive data types. Notably, future methods can’t take standard or
custom objects as arguments. A common pattern is to pass the method a List of
record IDs that you want to process asynchronously.

Future methods are typically used for:

 Callouts to external Web services. If you are making callouts from a trigger or after
performing a DML operation, you must use a future or queueable method. A callout
in a trigger would hold the database connection open for the lifetime of the callout
and that is a "no-no" in a multitenant environment.
 Operations you want to run in their own thread, when time permits such as some sort
of resource-intensive calculation or processing of records.
 Isolating DML operations on different sObject types to prevent the mixed DML error.
This is somewhat of an edge-case but you may occasionally run across this issue.
See sObjects That Cannot Be Used Together in DML Operations for more details.
 The reason why objects can’t be passed as arguments to future methods is
because the object can change between the time you call the method and the
time that it actually executes. Remember, future methods are executed when
system resources become available. In this case, the future method may have
an old object value when it actually executes, which can cause all sorts of bad
things to happen.
 It’s important to note that future methods are not guaranteed to execute in
the same order as they are called. Again, future methods are not guaranteed
to execute in the same order as they are called. When using future methods,
it’s also possible that two future methods could run concurrently, which could
result in record locking and a nasty runtime error if the two methods were
updating the same record.

Test Classes-@isTest
Testing future methods is a little different than typical Apex testing. To test future
methods, enclose your test code between the startTest and stopTest test methods.
The system collects all asynchronous calls made after the startTest. When stopTest is
executed, all these collected asynchronous processes are then run synchronously.
You can then assert that the asynchronous call operated properly.

Things to Remember
Future methods are a great tool, but with great power comes great responsibility.
Here are some things to keep in mind when using them:

 Methods with the future annotation must be static methods, and can only
return a void type.
 The specified parameters must be primitive data types, arrays of primitive data
types, or collections of primitive data types; future methods can’t take objects
as arguments.
 Future methods won’t necessarily execute in the same order they are called. In
addition, it’s possible that two future methods could run concurrently, which
could result in record locking if the two methods were updating the same
record.
 Future methods can’t be used in Visualforce controllers in getMethodName(),
setMethodName(), nor in the constructor.
 You can’t call a future method from a future method. Nor can you invoke a
trigger that calls a future method while running a future method. See the link
in the Resources for preventing recursive future method calls.
 The getContent() and getContentAsPDF() methods can’t be used in methods
with the future annotation.
 You’re limited to 50 future calls per Apex invocation, and there’s an additional
limit on the number of calls in a 24-hour period. For more information on
limits, see the link below.
Test code cannot actually send callouts to external systems, so you’ll have to ‘mock’
the callout for test coverage. Check out the Apex Integration Services module for
complete details on mocking callouts for testing.

Batch Apex
Batch Apex is used to run large jobs (think thousands or millions of records!) that
would exceed normal processing limits. Using Batch Apex, you can process records
asynchronously in batches (hence the name, “Batch Apex”) to stay within platform
limits. If you have a lot of records to process, for example, data cleansing or
archiving, Batch Apex is probably your best solution.

Here’s how Batch Apex works under the hood. Let’s say you want to process 1 million
records using Batch Apex. The execution logic of the batch class is called once for
each batch of records you are processing. Each time you invoke a batch class, the job
is placed on the Apex job queue and is executed as a discrete transaction. This
functionality has two awesome advantages:

 Every transaction starts with a new set of governor limits, making it easier to
ensure that your code stays within the governor execution limits.
 If one batch fails to process successfully, all other successful batch
transactions aren’t rolled back.

Batch Apex Syntax


To write a Batch Apex class, your class must implement the Database.Batchable
interface and include the following three methods:

start

Used to collect the records or objects to be passed to the interface method execute
for processing. This method is called once at the beginning of a Batch Apex job and
returns either a Database.QueryLocator object or an Iterable that contains the
records or objects passed to the job.

Most of the time a QueryLocator does the trick with a simple SOQL query to
generate the scope of objects in the batch job. But if you need to do something
crazy like loop through the results of an API call or pre-process records before being
passed to the execute method, you might want to check out the Custom Iterators
link in the Resources section.
With the QueryLocator object, the governor limit for the total number of records
retrieved by SOQL queries is bypassed and you can query up to 50 million records.
However, with an Iterable, the governor limit for the total number of records
retrieved by SOQL queries is still enforced.

execute

Performs the actual processing for each chunk or “batch” of data passed to the
method. The default batch size is 200 records. Batches of records are not guaranteed
to execute in the order they are received from the start method.

This method takes the following:

 A reference to the Database.BatchableContext object.


 A list of sObjects, such as List<sObject>, or a list of parameterized types. If
you are using a Database.QueryLocator, use the returned list.

finish
Used to execute post-processing operations (for example, sending an email) and is
called once after all batches are processed.

Here’s what the skeleton of a Batch Apex class looks like:

public class MyBatchClass implements Database.Batchable<sObject> {

public (Database.QueryLocator | Iterable<sObject>)


start(Database.BatchableContext bc) {

// collect the batches of records or objects to be passed to


execute

public void execute(Database.BatchableContext bc, List<P> records){

// process each batch of records

public void finish(Database.BatchableContext bc){

// execute any post-processing operations

Copy
Invoking a Batch Class
To invoke a batch class, simply instantiate it and then call Database.executeBatch
with the instance:

MyBatchClass myBatchObject = new MyBatchClass();

Id batchId = Database.executeBatch(myBatchObject);

Copy

You can also optionally pass a second scope parameter to specify the number of
records that should be passed into the execute method for each batch. Pro tip: you
might want to limit this batch size if you are running into governor limits.

Id batchId = Database.executeBatch(myBatchObject, 100);

Each batch Apex invocation creates an AsyncApexJob record so that you can track
the job’s progress. You can view the progress via SOQL or manage your job in the
Apex Job Queue. We’ll talk about the Job Queue shortly.

AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, TotalJobItems,


NumberOfErrors FROM AsyncApexJob WHERE ID = :batchId ];

Sample Batch Apex Code


Now that you know how to write a Batch Apex class, let’s see a practical example.
Let’s say you have a business requirement that states that all contacts for companies
in the USA must have their parent company’s billing address as their mailing address.
Unfortunately, users are entering new contacts without the correct addresses! Will
users never learn?! Write a Batch Apex class that ensures that this requirement is
enforced.

The following sample class finds all account records that are passed in by the start()
method using a QueryLocator and updates the associated contacts with their
account’s mailing address. Finally, it sends off an email with the results of the bulk
job and, since we are using Database.Stateful to track state, the number of records
updated.

public class UpdateContactAddresses implements

Database.Batchable<sObject>, Database.Stateful {
// instance member to retain state across transactions

public Integer recordsProcessed = 0;

public Database.QueryLocator start(Database.BatchableContext bc) {

return Database.getQueryLocator(

'SELECT ID, BillingStreet, BillingCity, BillingState, ' +

'BillingPostalCode, (SELECT ID, MailingStreet, MailingCity, ' +

'MailingState, MailingPostalCode FROM Contacts) FROM Account ' +

'Where BillingCountry = \'USA\''

);

public void execute(Database.BatchableContext bc, List<Account> scope){

// process each batch of records

List<Contact> contacts = new List<Contact>();

for (Account account : scope) {

for (Contact contact : account.contacts) {

contact.MailingStreet = account.BillingStreet;

contact.MailingCity = account.BillingCity;

contact.MailingState = account.BillingState;

contact.MailingPostalCode = account.BillingPostalCode;

// add contact to list to be updated

contacts.add(contact);

// increment the instance member counter

recordsProcessed = recordsProcessed + 1;
}

update contacts;

public void finish(Database.BatchableContext bc){

System.debug(recordsProcessed + ' records processed. Shazam!');

AsyncApexJob job = [SELECT Id, Status, NumberOfErrors,

JobItemsProcessed,

TotalJobItems, CreatedBy.Email

FROM AsyncApexJob

WHERE Id = :bc.getJobId()];

// call some utility to send email

EmailUtils.sendMessage(job, recordsProcessed);

Best Practices
As with future methods, there are a few things you want to keep in mind when using
Batch Apex. To ensure fast execution of batch jobs, minimize Web service callout
times and tune queries used in your batch Apex code. The longer the batch job
executes, the more likely other queued jobs are delayed when many jobs are in the
queue. Best practices include:

 Only use Batch Apex if you have more than one batch of records. If you don't
have enough records to run more than one batch, you are probably better off
using Queueable Apex.
 Tune any SOQL query to gather the records to execute as quickly as possible.
 Minimize the number of asynchronous requests created to minimize the
chance of delays.
 Use extreme care if you are planning to invoke a batch job from a trigger. You
must be able to guarantee that the trigger won’t add more batch jobs than
the limit.

Queueable Apex
Released in Winter '15, Queueable Apex is essentially a superset of future methods
with some extra #awesomesauce. We took the simplicity of future methods and the
power of Batch Apex and mixed them together to form Queueable Apex! It gives you
a class structure that the platform serializes for you, a simplified interface without
start and finish methods and even allows you to utilize more than just primitive
arguments! It is called by a simple System.enqueueJob() method, which returns a job
ID that you can monitor. It beats sliced bread hands down!

Queueable Apex allows you to submit jobs for asynchronous processing similar to
future methods with the following additional benefits:

 Non-primitive types: Your Queueable class can contain member variables of


non-primitive data types, such as sObjects or custom Apex types. Those
objects can be accessed when the job executes.
 Monitoring: When you submit your job by invoking the System.enqueueJob
method, the method returns the ID of the AsyncApexJob record. You can use
this ID to identify your job and monitor its progress, either through the
Salesforce user interface in the Apex Jobs page, or programmatically by
querying your record from AsyncApexJob.
 Chaining jobs: You can chain one job to another job by starting a second job
from a running job. Chaining jobs is useful if you need to do some sequential
processing.

Queueable Versus Future


Because queueable methods are functionally equivalent to future methods, most of
the time you’ll probably want to use queueable instead of future methods. However,
this doesn’t necessarily mean you should go back and refactor all your future
methods right now.

Another reason to use future methods instead of queueable is when your


functionality is sometimes executed synchronously, and sometimes asynchronously.
It’s much easier to refactor a method in this manner than converting to a queueable
class. This is handy when you discover that part of your existing code needs to be
moved to async execution. You can simply create a similar future method that wraps
your synchronous method like so:

@future

static void myFutureMethod(List<String> params) {

// call synchronous method

mySyncMethod(params);

Copy

Queueable Syntax
To use Queueable Apex, simply implement the Queueable interface.

public class SomeClass implements Queueable {

public void execute(QueueableContext context) {

// awesome code here

Things to Remember
Queueable Apex is a great new tool but there are a few things to watch out for:

 The execution of a queued job counts once against the shared limit for asynchronous
Apex method executions.
 You can add up to 50 jobs to the queue with System.enqueueJob in a single
transaction.
 When chaining jobs, you can add only one job from an executing job with
System.enqueueJob, which means that only one child job can exist for each parent
queueable job. Starting multiple child jobs from the same queueable job is a no-no.
 No limit is enforced on the depth of chained jobs, which means that you can chain
one job to another job and repeat this process with each new child job to link it to a
new child job. However, for Developer Edition and Trial orgs, the maximum stack
depth for chained jobs is 5, which means that you can chain jobs four times and the
maximum number of jobs in the chain is 5, including the initial parent queueable job.
Scheduled Apex Syntax
To invoke Apex classes to run at specific times, first implement the Schedulable
interface for the class. Then, schedule an instance of the class to run at a specific time
using the System.schedule method.

global class SomeClass implements Schedulable {

global void execute(SchedulableContext ctx) {

// awesome code here

Copy

The class implements the Schedulable interface and must implement the only
method that this interface contains, which is the execute method.

The parameter of this method is a SchedulableContext object. After a class has been
scheduled, a CronTrigger object is created that represents the scheduled job. It
provides a getTriggerId method that returns the ID of a CronTrigger API object.

Sample Code
This class queries for open opportunities that should have closed by the current date,
and creates a task on each one to remind the owner to update the opportunity.

global class RemindOpptyOwners implements Schedulable {

global void execute(SchedulableContext ctx) {

List<Opportunity> opptys = [SELECT Id, Name, OwnerId, CloseDate

FROM Opportunity

WHERE IsClosed = False AND

CloseDate < TODAY];

// Create a task for each opportunity in the list

TaskUtils.remindOwners(opptys);

}
Copy

You can schedule your class to run either programmatically or from the Apex
Scheduler UI.

Using the System.Schedule Method


After you implement a class with the Schedulable interface, use the System.Schedule
method to execute it. The System.Schedule method uses the user's timezone for the
basis of all schedules, but runs in system mode—all classes are executed, whether or
not the user has permission to execute the class.

Use extreme care if you’re planning to schedule a class from a trigger. You must be
able to guarantee that the trigger won’t add more scheduled job classes than the
limit. In particular, consider API bulk updates, import wizards, mass record changes
through the user interface, and all cases where more than one record can be updated
at a time.

The System.Schedule method takes three arguments: a name for the job, a CRON
expression used to represent the time and date the job is scheduled to run, and the
name of the class.

RemindOpptyOwners reminder = new RemindOpptyOwners();

// Seconds Minutes Hours Day_of_month Month Day_of_week optional_year

String sch = '20 30 8 10 2 ?';

String jobID = System.schedule('Remind Opp Owners', sch, reminder);

Copy

For more information on the CRON expression used for scheduling, see the “Using
the System.Schedule Method” section in Apex Scheduler.

Scheduling a Job from the UI


You can also schedule a class using the user interface.

1. From Setup, enter Apex in the Quick Find box, then select Apex Classes.
2. Click Schedule Apex.
3. For the job name, enter something like Daily Oppty Reminder.
4. Click the lookup button next to Apex class and enter * for the search term to get a list
of all classes that can be scheduled. In the search results, click the name of your
scheduled class.
5. Select Weekly or Monthly for the frequency and set the frequency desired.
6. Select the start and end dates, and a preferred start time.
7. Click Save.

Code Coverage Requirement for Deployment

Before you can deploy your code or package it for the Lightning Platform
AppExchange, at least 75% of Apex code must be covered by tests, and all those
tests must pass. In addition, each trigger must have some coverage. Even though
code coverage is a requirement for deployment, don’t write tests only to meet this
requirement. Make sure to test the common use cases in your app, including positive
and negative test cases, and bulk and single-record processing.

Test Method Syntax

Test methods take no arguments and have the following syntax:

@isTest static void testName() {

// code_block

Copy

Alternatively, a test method can have this syntax:

static testMethod void testName() {

// code_block

Copy

Using the  isTest  annotation instead of the  testMethod  keyword is more flexible as
you can specify parameters in the annotation. We’ll cover one such parameter later.
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.

Test methods must be defined in test classes, which are classes annotated
with  isTest . This sample class shows a definition of a test class with one test method.

@isTest

private class MyTestClass {

@isTest static void myTest() {

// code_block

Copy

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, which are covered later.

Unit Test Example: Test the TemperatureConverter Class

The following simple example is of a test class with three test methods. The class
method that’s being tested takes a temperature in Fahrenheit as an input. It converts
this temperature to Celsius and returns the converted result. Let’s add the custom
class and its test class.

1. In the Developer Console, click File | New | Apex Class, and


enter TemperatureConverter for the class name, and then click OK.

2. Replace the default class body with the following.

3. public class TemperatureConverter {

4. // Takes a Fahrenheit temperature and returns the Celsius


equivalent.

5. public static Decimal FahrenheitToCelsius(Decimal fh) {

6. Decimal cs = (fh - 32) * 5/9;


7. return cs.setScale(2);

8. }

Copy

9. Press Ctrl+S to save your class.

10. Repeat the previous steps to create the TemperatureConverterTest class. Add


the following for this class.

11. @isTest

12. private class TemperatureConverterTest {

13. @isTest static void testWarmTemp() {

14. Decimal celsius =


TemperatureConverter.FahrenheitToCelsius(70);

15. System.assertEquals(21.11,celsius);

16. }

17.

18. @isTest static void testFreezingPoint() {

19. Decimal celsius =


TemperatureConverter.FahrenheitToCelsius(32);

20. System.assertEquals(0,celsius);

21. }

22. @isTest static void testBoilingPoint() {

23. Decimal celsius =


TemperatureConverter.FahrenheitToCelsius(212);

24. System.assertEquals(100,celsius,'Boiling point temperature


is not expected.');

25. }

26.
27. @isTest static void testNegativeTemp() {

28. Decimal celsius =


TemperatureConverter.FahrenheitToCelsius(-10);

29. System.assertEquals(-23.33,celsius);

30. }

31.

Copy

The  TemperatureConverterTest  test class verifies that the method works as expected by
calling it with different inputs for the temperature in Fahrenheit. Each test method
verifies one type of input: a warm temperature, the freezing point temperature, the
boiling point temperature, and a negative temperature. 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, which is used in  testBoilingPoint() . This optional string is logged if the
assertion fails.

Let’s run the methods in this class.

1. In the Developer Console, click Test | New Run.

2. Under Test Classes, click TemperatureConverterTest.

3. To add all the test methods in the  TemperatureConverterTest  class to the test
run, click Add Selected.

4. Click Run.

5. 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. In this example, the class you’ve tested,
the  TemperatureConverter  class, has 100% coverage, as shown in this image.

Note

Whenever you modify your Apex code, rerun your tests to refresh code coverage
results.

A known issue with the Developer Console prevents it from updating code coverage
correctly when running a subset of tests. To update your code coverage results,
use Test | Run All rather than Test | New Run.

While one test method would have resulted in full coverage of


the  TemperatureConverter  class, it’s still important to test for different inputs to ensure
the quality of your code. Obviously, it isn’t possible to verify every data point, but
you can test for common data points and different ranges of input. For example, you
can verify passing positive and negative numbers, boundary values, and invalid
parameter values to verify negative behavior. The tests for
the  TemperatureConverter  class verify common data points, like the boiling
temperature, and negative temperatures.

The  TemperatureConverterTest  test class doesn’t cover invalid inputs or boundary


conditions. Boundary conditions are about minimum and maximum values. In this
case, the temperature conversion method accepts a Decimal, which can accept large
numbers, higher than Double values. For invalid inputs, there is no invalid
temperature but the only invalid input is  null . How does the conversion method
handle this value? In this case, when the Apex runtime dereferences the parameter
variable to evaluate the formula, it throws a  System.NullPointerException . You can
modify the  FahrenheitToCelsius()  method to check for an invalid input and
return  null  in that case, and then add a test to verify the invalid input behavior.

Up to this point, all tests pass because the conversion formula used in the class
method is correct. But that’s boring! Let’s try to simulate a failure just to see what
happens when an assertion fails. For example, let’s modify the boiling point
temperature test and pass in a false expected value for the boiling point Celsius
temperature (0 instead of 100). This causes the corresponding test method to fail.

1. Change the  testBoilingPoint()  test method to the following.

2. @isTest static void testBoilingPoint() {

3. Decimal celsius =
TemperatureConverter.FahrenheitToCelsius(212);

4. // Simulate failure

5. System.assertEquals(0,celsius,'Boiling point temperature is


not expected.');

Copy

6. To execute the same test run, click the latest run in the Tests tab, and then
click Test | Rerun.

The assertion in  testBoilingPoint()  fails and throws a fatal error (an
AssertException that can’t be caught).

7. Check the results in the Tests tab by expanding the latest test run. The test run
reports one out of four tests failed. To get more details about the failure,
double-click the test run.

Detailed results appear in a separate tab as shown in this image.


8. To get the error message for the test failure, double-click inside the Errors
column for the failed test. You’ll see the following. The descriptive text next
to  Assertion Failed:  is the text we provided in
the  System.assertEquals()  statement.

System.AssertException: Assertion Failed: Boiling point temperature is not


expected.: Expected: 0, Actual: 100.00

The test data in these test methods are numbers and not Salesforce records. You’ll
find out more about how to test Salesforce records and how to set up your data in
the next unit.

Increase Your Code Coverage

When writing tests, try to achieve the highest code coverage possible. Don’t just aim
for 75% coverage, which is the lowest coverage that the Lightning Platform requires
for deployments and packages. The more test cases that your tests cover, the higher
the likelihood that your code is robust. Sometimes, even after you write test methods
for all your class methods, code coverage is not at 100%. One common cause is not
covering all data values for conditional code execution. For example, some data
values tend to be ignored when your class method has  if  statements that cause
different branches to be executed based on whether the evaluated condition is met.
Ensure that your test methods account for these different values.
This example includes the class method,  getTaskPriority() , which contains
two  if  statements. The main task of this method is to return a priority string value
based on the given lead state. The method validates the state first and returns  null  if
the state is invalid. If the state is CA, the method returns 'High'; otherwise, it returns
'Normal' for any other state value.

public class TaskUtil {

public static String getTaskPriority(String leadState) {

// Validate input

if (String.isBlank(leadState) || leadState.length() > 2) {

return null;

String taskPriority;

if (leadState == 'CA') {

taskPriority = 'High';

} else {

taskPriority = 'Normal';

return taskPriority;

Copy
Note
The equality operator ( == ) performs case-insensitive string comparisons, so there is
no need to convert the string to lower case first. This means that passing
in  'ca'  or  'Ca'  will satisfy the equality condition with the string literal  'CA' .

This is the test class for the  getTaskPriority()  method. The test method simply
calls  getTaskPriority()  with one state ( 'NY' ).

@isTest

private class TaskUtilTest {

@isTest static void testTaskPriority() {

String pri = TaskUtil.getTaskPriority('NY');

System.assertEquals('Normal', pri);

Copy

Let’s run this test class ( TaskUtilTest ) in the Developer Console and check code
coverage for the corresponding  TaskUtil  class that this test covers. After the test run
finishes, the code coverage for  TaskUtil  is shown as 75%. If you open this class in the
Developer Console, you see six blue (covered) lines and two red (uncovered) lines, as
shown in this image.
The reason why line 5 wasn’t covered is because our test class didn’t contain a test to
pass an invalid state parameter. Similarly, line 11 wasn’t covered because the test
method didn’t pass  'CA'  as the state. Let’s add two more test methods to cover
those scenarios. The following shows the full test class after adding
the  testTaskHighPriority()  and  testTaskPriorityInvalid()  test methods. If you rerun
this test class, the code coverage for  TaskUtil  is now at 100%!

@isTest

private class TaskUtilTest {

@isTest static void testTaskPriority() {

String pri = TaskUtil.getTaskPriority('NY');

System.assertEquals('Normal', pri);

@isTest static void testTaskHighPriority() {


String pri = TaskUtil.getTaskPriority('CA');

System.assertEquals('High', pri);

@isTest static void testTaskPriorityInvalid() {

String pri = TaskUtil.getTaskPriority('Montana');

System.assertEquals(null, pri);

Copy

Create and Execute a Test Suite

A test suite is a collection of Apex test classes that you run together. For example,
create a suite of tests that you run every time you prepare for a deployment or
Salesforce releases a new version. Set up a test suite in the Developer Console to
define a set of test classes that you execute together regularly.

You now have two test classes in your org. These two classes aren’t related, but let’s
pretend for the moment that they are. Assume that there are situations when you
want to run these two test classes but don’t want to run all the tests in your org.
Create a test suite that contains both classes, and then execute the tests in the suite.

1. In the Developer Console, select Test | New Suite.

2. Enter TempConverterTaskUtilSuite for the suite name, and then click OK.

3. Select TaskUtilTest, hold down the Ctrl key, and then


select TemperatureConverterTest.

4. To add the selected test classes to the suite, click >.


5. Click Save.

6. Select Test | New Suite Run.

7. Select TempConverterTaskUtilSuite, and then click > to


move  TempConverterTaskUtilSuite  to the Selected Test Suites column.

8. Click Run Suites.

9. On the Tests tab, monitor 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. Like in a run of individual test methods, you can double-click
method names to see detailed test results.

Creating Test Data

Salesforce records that are created in test methods aren’t committed to the
database. They’re rolled back when the test finishes execution. This rollback behavior
is handy for testing because you don’t have to clean up your test data after the test
executes.

By default, Apex tests don’t have access to pre-existing data in the org, except for
access to setup and metadata objects, such as the User or Profile objects. Set up test
data for your tests. Creating test data makes your tests more robust and prevents
failures that are caused by missing or changed data in the org. You can create test
data directly in your test method, or by using a utility test class as you’ll find out
later.
Note
Even though it is not a best practice to do so, there are times when a test method
needs access to pre-existing data. To access org data, annotate the test method
with  @isTest(SeeAllData=true) . The test method examples in this unit don’t access org
data and therefore don’t use the  SeeAllData  parameter.

You might also like