So far we have been writing code on the fly, without worrying if the code would break and that's really not a good practice. In production, your users will find interestingly creative ways to break your code and I learnt it the hard way (on other platforms). Salesforce doesn't allow developers to write code directly in production. Instead, all of the code goes into the developer environment first and must have 75% code coverage. This means, at least 75% of every single line of code written must be executed by the test class and are not counted towards the code limit of the org.

Test Class Basics

Every test class is marked as test by using @isTest before declaring the class and for better structure, every test class keeps the same name as the class we are writing it for and has the word Test at the end of it. Let's write our test class for FirstName Class:

@isTest
public class FirstNameTest{

}

Every method we write for our test class is marked with testmethod:

@isTest
public class FirstNameTest{
	static testmethod void methodName(){
	}
}

Alternatively, test methods can be marked with @isTest too:

@isTest
public class FirstnameTest{
	@isTest static void methodName(){
	}
}

Both ways of declaring a test method are correct and is compeltely on you to choose. I prefer using the testmethod keyword unless there is a need to use @isTest parameters. We cover it later in the course. The visibility (public/private/global) of the method doesn't matter and is ommited.

Just like System.debug() is essential to output logs in console, we use System.assertEquals() to compare two values, and it returns a Boolean. There are two ways of using it:

System.assertEquals(expectedValue, givenValue);

System.assetEquals(expectedValue, givenValue, String_If_Value_Is_False);

System.assertEquals compares expectedValue and givenValue. If the values are same it returns true and false if they are different. Let's look at few examples:

Integer a = 1, b = 2;
System.assertEquals(a, b); //This will be false

When you run this in Exec Anon window, you'll be greeted with an error:

Line: 2, Column: 1
System.AssertException: Assertion Failed: The values are not same: Expected: 1, Actual: 2

On line 2 when we compare values of a and b, the console was expecting the value of b to be 1, but got 2 instead. Now let's replace the value of a with 2 instead and check:

Integer b = 2;
System.assertEquals(2, b);

To see the optional String parameter in action, we have to write a class. Let's write a class calculator that has a method addition which takes two Integer as parameter and adds them and sees if the final result is 4.

public class Calculator {

    public static void addition(Integer a, Integer b){
        Integer c = a + b;
        System.assertEquals(4, c, 'The result is not 4');
    }

}

Now in Exec Anon window let's call the class. First, let's add 2 and 2 so we know our code actually runs"

calculator.addition(2, 2);

Great! Now let's add 2 and 5.

calculator.addition(2, 5);

You'll notice you'll be greeted with an error:

Line: 5, Column: 1
System.AssertException: Assertion Failed: The result is not 4: Expected: 4, Actual: 7

We wanted this code block to fail and that's exactly what happened The result is not 4 is the third optional string fromSystem.assertEqual() .

Now that we know most of the basics, let's write a test class for an Apex Class.

Tests for Apex Class

Let's use the same caluclator class but add in multiplication method too! The Calculator class should look something like this:

public class Calculator{

    public static Integer addition(Integer a, Integer b){
        return a+b;
    }

    public static Integer multiplication(Integer a, Integer b){
        return a*b;
    }
}

The addition method returns the sum of a and b, while the multiplication method returns the product of a and b. Now let's write a test class. For this, create a new class and name it CalculatorTest and mark it as a test class using the @isTest. Now, let's write our additionTest method that checks for values using System.assertEquals().

@isTest
public class CalculatorTest {
    static testmethod void additionTest(){
        Integer result = Calculator.addition(2,2);
        System.assertEquals(4, result, 'Unexpected result');
    }

}

Let's break this code down first:

  • We create a test class named CalculatorTest
  • We have a testmethod called additionTest()
  • We save the result from Calculator.addition in an Integer called result
  • We compare values to see if the result we were expecting (4) was the same as what the class returned (result)

Now to run our test, we go to Test menu of Developer Console, and click on New Run. This will open a new popup. Select the CalculatorTest from the left most window pane, then select additionTest and click on Run on bottom right.

Test Popup

To see the results of the test, we select the Test in the bottom pane. This will show all the tests we ran this session. Click on the test run and select the Calculator from the bottom right pane.

Test Bottom Space

Notice how it says

Class Percent Lines
Calculator 50% 2/4

This means out of the 4 lines of code we wrote in our Calculator class (class declarations are not counted here, only the user written code), only 2 lines of code ran. Double clicking on this would open up the class and highlight what lines of code ran and what didn't. This is particularly useful to see what lines of code were missed out and in our case, the two lines of multiplication method didn't run. Let's add to our Test Classs to cover it.

@isTest
public class CalculatorTest {
    static testmethod void additionTest(){
        Integer result = Calculator.addition(2,2);
        System.assertEquals(4, result, 'Unexpected result');
    }

    static testmethod void multiplicationTest(){
        Integer result = Calculator.multiplication(2,3);
        System.assertEquals(6, result, 'Unexpected result');
    }

}

Head over to Test and Run a new test to see the results. Now we have 100% code coverage! To practice more, write tests for classes we have written through out the course.

Use Test > Clear Test Data to clear your test logs.

Tests for Batch Class

Let's write a test class for our ClassBatch Class we wrote earlier in this course. Incase you forgot, here's the code:

global class ClassBatch implements Database.Batchable<sObject>{


    global iterable<sObject> start(Database.BatchableContext bc){
    	 //return all contacts where the last name is Test

        String qString = 'SELECT ID, LastName FROM Contact WHERE LastName = \'Test\'';
        return Database.query(qString);
    }

    global void execute(Database.BatchableContext bc, List<Contact> conList){
		 //Make the first name and last name the same

        List<Contact> finalList = new List<Contact>();
        for (Contact iterator: conList){
            iterator.firstName = iterator.LastName;
        }
        update finalList;
    }

    global void finish(Database.BatchableContext bc){
        System.debug('Batch successfully executed');
    }

}

Writing test methods for Batch classes is a little different than writing for regular classes. When we run a test class, it creates data in a separate database from the org to avoid changes being made on the production database. Think of it like this, when you are trying a new way of making coffee, like shifting from using the good old french press to using an espresso machine, you make much smaller quantity than usual to avoid wasting your coffee and taste testing the new method.

To write a test class for our ClassBatch, we first make a new class file.

@isTest
public class ClassBatchTest {

}

Now we need to create some test data. For this, we need to create a new method and declare it as @testSetup. Marking a method with @testSetup makes it the first method to run in the test class, despite it's position in the code (Remember code always runs line by line).

@isTest
public class ClassBatchTest {

    @testSetup static void createTestData(){

    }

}

Now let's make a loop that inserts 3 contacts with the last name Test and name Name 1, Name 2 and Name 3.

@isTest
public class ClassBatchTest {

    @testSetup static void createTestData(){
		List<Contact> finalList = new List<Contact>();
        for(Integer i = 1; i<=3; i++){
            Contact iterator = new Contact();
            iterator.firstName = 'Name '+ i;
            iterator.lastName = 'Test';
            finalList.add(iterator);
        }
        insert finalList;

    }

}

This inserts three records in our test database that's not directly accessible from the org because the database is only available as the testing process is running and is destroyed after it's done.

Now, let's write our testmethod method. We will be using the Test.startTest() and Test.stopTest(); here. These methods denote when the test is starting/ending.

@isTest
public class ClassBatchTest {

    @testSetup static void createTestData(){
		List<Contact> finalList = new List<Contact>();
        for(Integer i = 1; i<=3; i++){
            Contact iterator = new Contact();
            iterator.firstName = 'Name '+ i;
            iterator.lastName = 'Test';
            finalList.add(iterator);
        }
        insert finalList;

    }

    static testMethod void batchTester(){

        Test.startTest();
        ClassBatch tester = new ClassBatch();
        Id jobId = Database.executeBatch(tester);
        Test.stopTest();

        System.assertEquals(3, [SELECT count() FROM Contact WHERE LastName = 'Test'], 'Last Name error');
        System.assertEquals(3, [SELECT count() FROM Contact WHERE FirstName = 'Test'], 'First Name error');
        System.assertEquals(3, [SELECT count() FROM Contact WHERE LastName = 'Test' AND FirstName = 'Test'], 'Names are not equal error');

    }

}

Let's breakdown the batchTester() method:

  • We run our batch class inside the Test.startTest() and Test.stopTest() because we want to run it as a test and reset governor limits.
  • We use three System.assertEquals() to ensure our test ran successfully:
    • The first one checks to see if the 3 new records we created were succesfully inserted.
    • The second one checks if the first names were updated after we ran the batch class, denoting the batch class ran successfully
    • The third one is a redudant check to ensure contacts where first and last name are test exist in the database and result to 3 because that's the number of contacts we inserted in the test database.

This might seem complicated at first, but really, all we are doing is creating dummy content and seeing if the values it returns is same as the expected value.

Summary

  • Test classes are necessary and must have 75% code coverage before deploying to production
  • System.assertEquals compares a given value to expected value and returns Boolean based on the outcome
  • All test classes must be marked with @isTest
  • All test methods must be marked with testMethod or @isTest
  • Test.startTest() and Test.stopTest() denote start/stop of a test insdie a method
Day 19: Apex Test Class Test