Salesforce - Developer - Testing

salesforce-developer

https://trailhead.salesforce.com/force_com_dev_beginner/apex_testing/apex_testing_intro
https://trailhead.salesforce.com/force_com_dev_beginner/apex_testing/apex_testing_triggers
https://trailhead.salesforce.com/force_com_dev_beginner/apex_testing/apex_testing_data
https://developer.salesforce.com/page/An_Introduction_to_Apex_Code_Test_Methods
http://bit.ly/WebServiceTestingSession
http://bit.ly/ApexCalloutTestBlog
http://bit.ly/1HtXk2B
https://gearset.com/blog/6-practical-tips-to-improve-your-salesforce-tests
https://gearset.com/blog/implementing-salesforce-testing-best-practice-with-gearset
https://developer.salesforce.com/page/Apex_Enterprise_Patterns_-_Separation_of_Concerns
http://jessealtman.com/2014/03/dependency-injection-in-apex
http://github.com/financialforcedev/fflib-apex-mocks

// Salesforce - Developer - Testing

@isTest
private class OnlineAppHomeControllerTest {

  public static testmethod void itShouldInit() {
  }

}

Best practices for Salesforce unit tests:

1. Isolation - Ensure that you turn off seeAllData
2. Arrange - Act - Assert
3. Run tests as real user profiles
4. test.startTest / test.stopTest
5. Exercise bulk trigger functionality
6. Make sure that you do not get random failures if data changes.
7. Make sure that your trigger can handle bulk.  Aim for 10 to 20 objects

Principles of testing:

1. Use assertions
2. Use Test.startTest() and Test.stopTest()
3. Do Positive Tests
4. Do Negative Tests
5. Do User Tests
6. Use your own test data.  Unless you have to, never use seeAllData=true
7. Use a domain specific test helper library
8. Use Mocking
9. Write code that make testing easier.
 1. Write samll, tightly focused methods that play well with others.
     This also means that we should write code that are loosely-coupled.
     Compose advanced functionality by using small methods / units together.
 2. Keep our methods short, no more than 20 lines.  
 3. Keep method arguments to a minimum, no more than four.
 4. Write our visualforce controllers to use a single wrapper object.
     Either mock this wrapper object, or write a test helper method to construct it.
10. Use continuous integration

System.assert(boolean-expression, 'friendly message');
System.assertEquals(expected, actual, 'friendly message');
System.assertNotEquals(expected, actual, 'friendly message');

We can also write our own assertion methods:

@IsTest
public class customAssertions {
  public class CustomAssertionException extends Exception{}
  public Boolean assertDuplicateAccount(Account a, Account b) {
    // compare accounts
    if (duplicate != true) {
      throw new CustomAssertionException('Objects are not the same.');
    }
    return true;
  }
}

Test.startTest() reset DML, CPU Time, and other governor limits, ensuring
any limits you hit come from the code that you are testing.  Test.stopTest() 
forces asynchronous code to complete.

The assertions should be done after Test.stopTest().

Positive tests test the expected behaviors.  Our code may have multiple
expected behaviors.

Negative tests prove that our code properly handles exception and errors.  The
general pattern is to call our method within a try/catch block in the test.  
Since we expect an exception, we'll catch it in the catch block.

User Tests is the most complex form of testing.  It proves your security model.  
Test with users of different roles / profiles and permission sets.  The pattern 
works like this: Create a user with a given profile, assign appropriate 
permission sets.  Test both positive and negative users.

@IsTest(seeAllData=false)
public class Quote_Moved_To_Opportunity_Correctly {

}

Arrange
test.startTest(); // Gives us a new set of governor limits
Act
test.stopTest();
Asserts

Limitations:

1. Code coverage does not necessarily mean good tests.
2. Changes made by end users can cause silent test failures.
3. Lack of automation, but we can use Gearset

Gearset:

1. Automate unit tests with a few clicks
2. Track code coverage and test status
3. Quickly debug failures
4. Email / SMS / Chatter / Slack alert of test status changes.
5. Track all tests across all orgs in one place
6. Completely free.

http://gearset.com

trigger contactTest on Contact (before insert, before update) {
  for (Contact ct: Trigger.new) {
    Account acct = [select id, name from Account where Id=:ct.AccountId];
    if (acct.BillingState=='CA') {
      System.debug('found a contact related to an account in california...');
      ct.email = 'test_email@testing.com';
      // Apply more logic here....
    }
  }
}

public class sampleTestMethodCls {
  static testMethod void testAccountTrigger(){
    //First, prepare 200 contacts for the test data
    Account acct = new Account(name='test account');
    insert acct;

    Contact[] contactsToCreate = new Contact[]{};

    for (Integer x = 0; x < 200; x++) {
      Contact ct = new Contact(AccountId=acct.Id,lastname='test');
      contactsToCreate.add(ct);
    }

    // Now insert data causing an contact trigger to fire. 
    Test.startTest();
    insert contactsToCreate;
    Test.stopTest();    
  }    
}

This test method creates an array of 200 contacts and inserts them. The insert 
will, in turn, cause the trigger to fire. When this test method is executed, a 
System.Exception will be thrown when it hits a governor limit. Since the trigger 
shown above executes a SOQL query for each contact in the batch, this test 
method throws the exception 'Too many SOQL queries: 101'. A trigger can only 
execute at most 100 queries.

Note the use of Test.startTest and Test.stopTest. When executing tests, code 
called before Test.startTest and after Test.stopTest receive a separate set of 
governor limits than the code called between Test.startTest and Test.stopTest. 
This allows for any data that needs to be setup to do so without affecting the 
governor limits available to the actual code being tested.

Now let's correct the trigger to properly handle bulk operations. The key to 
fixing this trigger is to get the SOQL query outside the for loop and only do 
one SOQL Query: 

trigger contactTest on Contact (before insert, before update) {
  Set<Id> accountIds = new Set<Id>();
  for (Contact ct: Trigger.new) {
    accountIds.add(ct.AccountId);
  }

  // Do SOQL Query
  Map<Id, Account> accounts = new Map<Id, Account>([
    select id, name, billingState 
    from Account 
    where id in :accountIds
  ]);

  for (Contact ct: Trigger.new) {
    if (accounts.get(ct.AccountId).BillingState=='CA') {
      System.debug('found a contact related to an account in california...');
      ct.email = 'test_email@testing.com';
      //Apply more logic here....
    }
  } 
}

Note how the SOQL query retrieving the accounts is now done once only. If you 
re-run the test method shown above, it will now execute successfully with no 
errors and 100% code coverage.
// Testing trigger exception
@IsTest
public class TestRestrictContactByName {
    static testmethod void test1() {
        try {
            Contact c = new Contact(lastname = 'INVALIDNAME ');
            insert c;        
        } catch (Exception e) {
            Boolean expectedExceptionThrown =  e.getMessage().contains('The Last Name "'+ 'INVALIDNAME' +'" is not allowed for DML') ? true : false;
            System.assertEquals(true, expectedExceptionThrown);
        }
        Contact c = new Contact(lastname = 'VALIDNAME ');
        insert c;
    }
}
// Another example
@IsTest
public class TestVerifyDate {
    static testmethod void testCheckDate() {
        System.debug('What is going on?');
        Date date1 = Date.today();
        Date date2 = date1.addDays(20);
        Date date3 = VerifyDate.CheckDates(date1, date2);
        System.assertEquals(date2, date3);

        date2 = date1.addDays(32);
        date3 = VerifyDate.CheckDates(date1, date2);
        Integer totalDays = Date.daysInMonth(date1.year(), date1.month());
        Date endOfMonth = Date.newInstance(date1.year(), date1.month(), totalDays);
        System.assertEquals(endOfMonth, date3);
    }
}
// Salesforce - Developer - Testing - Apex REST

@isTest
private class REST_AccountService_V6_Test {
  static testMethod void testDoGet_WithoutId() {
    setupTestData();

    Test.startTest();
    // Set up the RestContext object
    System.RestContext.request = new RestRequest();
    System.RestContext.response = new RestResponse();
    RestContext.request.requestURI = 
        'https://na15.salesforce.com/services/apexrest/v6/accounts/';  
    RestContext.request.httpMethod = 'GET';

    REST_AccountService_V6.AccountWrapper results = new REST_AccountService_V6.AccountWrapper();
    results = REST_AccountService_V6.doGet();
    Test.stopTest();

    system.assertEquals(results.status, 'Error');
    system.assertEquals(results.message,'You must specify an External Id.');
    system.assertEquals(System.RestContext.response.StatusCode, 400);
  }

  static testMethod void testDoGet_WithGoodId() {
    setupTestData();

    Test.startTest();
    // Set up the RestContext object
    System.RestContext.request = new RestRequest();
    System.RestContext.response = new RestResponse();
    RestContext.request.requestURI = 
        'https://na15.salesforce.com/services/apexrest/v6/accounts/1002';  
    RestContext.request.httpMethod = 'GET';

    REST_AccountService_V6.AccountWrapper results = new REST_AccountService_V6.AccountWrapper();
    results = REST_AccountService_V6.doGet();
    Test.stopTest();
    system.assertEquals(results.status, 'Success');
  }

  static testMethod void testDoGet_WithBadId() {
    setupTestData();

    Test.startTest();
    // Set up the RestContext object
    System.RestContext.request = new RestRequest();
    System.RestContext.response = new RestResponse();
    RestContext.request.requestURI = 'https://na15.salesforce.com/services/apexrest/v6/accounts/6550';  
    RestContext.request.httpMethod = 'GET';

    REST_AccountService_V6.AccountWrapper results = new REST_AccountService_V6.AccountWrapper();
    results = REST_AccountService_V6.doGet();
    Test.stopTest();

    system.assertEquals(results.status, 'Error');
    system.assertEquals(results.message,'This account could not be found, please try again.');
    system.assertEquals(System.RestContext.response.StatusCode, 404);
  }

  private static void setupTestData() {
    List<Account> accounts = new List<Account>();
    accounts.add(new Account(Name='Account 1', Phone='(111) 222-3344', Website='www.account1.com', External_Id__c='1000'));
    accounts.add(new Account(Name='Account 2', Phone='(222) 333-4455', Website='www.account1.com', External_Id__c='1001'));
    accounts.add(new Account(Name='Account 3', Phone='(333) 444-5566', Website='www.account1.com', External_Id__c='1002'));
    accounts.add(new Account(Name='Account 4', Phone='(444) 555-6677', Website='www.account1.com', External_Id__c='1003'));
    accounts.add(new Account(Name='Account 5', Phone='(555) 666-7788', Website='www.account1.com', External_Id__c='1004'));
    insert accounts;
  }
}

User u = AwesomeTestLib.getUserWithProfile('ProfileName');
Account a = (Account) TestFactory.createSObject(new Account());
Integer result;
System.runAs(u) {
  Test.startTest();
  result = drWho.getBankAccount(a);
  Test.stopTest();
}
// some assertions

TestFactory: this is an open-sourced test data factory from Daniel Hoechst.  The 
URL for this project is http://bit.ly/1c5exnV

Account a = (Account) TestFactory.createSObject(new Account());
Opportunity o = (Opportunity) TestFactory.createSObject(
  new Opportunity(AccountId = a.Id)
);

Account[] aList = (Account[]) TestFactory.createSObjectList(new Account(), 200);

A domain-specific test helper library make writing test faster, make test easier
to ready, by abstracting out superfluous code.  Mark your test helper with 
@IsTest to ensure that it's never called by live code, and to ensure that it is
not counted against your code coverage.

You can create your own test helper methods.  Make them public static methods so
that we can invoke these methods without first having to create an instance of
the class.

Mocks allow us to write true unit tests by mocking objects.  We have to setup 
the mock and it's repsonses.  We want to use mocks to insert objects whenever 
that object is not critical to the test.  For example, if we're testing a method
that populates an Account with the result of a web service, mock out the web 
service's result.  This allows us to test just what that method does to the 
account object, not it's ability to talk to a web service.

@IsTest
public class exampleCode_Tests {
  public Boolean MockExample(Account a, Account b) {
    fflib_apexMocks m = new ffLib_apexMocks();
    myInterface mock = new MockMyService(m);
    m.startStubbing();
    m.when(mock.add(5,7)).thenReturn(12);
    m.stopStubbing();

    Test.startTest();
    ExampleCode mockObj = new exampleCode(myAwesomeService);
    Integer result = mockObj.add(5,7);
    Test.stopTest();
    System.assertEqual(12, result, 'friendly message');
  }
}

public with sharing class HTTPMockCalloutFactory implements HttpCalloutMock {
  HTTPMockCalloutFactory (Integer code, String status, String body,
    Map<String,String> responseHeaders) {
      // set class variables here
    }
  public HttpResponse response(HttpRequest req) {
    res.setStatusCode(this.code);
    res.setStatus(this.status);
    res.setBody(this.bodyAsString);
    return res;
  }
}

We can even extend our factory to return different response based on a static 
class variable.  This is useful for testing multple callout scenarios.  Write 
an additional constructor that accepts a list of json strings that de-serialize 
into responses.

Continuous Integration is the idea that every code change committed to our 
source code repository triggers a full run of the tests.  A number of tools 
are available for CI on the platform: Travis.ci, Drone.io, Jenkins, Codeship.

CI gives us insight to tests that are failing as soon as they're committed.  
This keeps us aware of code coverage as development occurs.

Do not insert or query unless you have to.  You can often test with objects in 
memory.

Write and use a standardized logging library that wraps log data in a highly
visible header / footer.

We can only invoke Test.startTest and Test.stopTest once per test method.  If 
we need to test multiple outcomes, the work-around is to write multiple test 
methods (one test method for each outcome).
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License