Tests and Testability
Apex Structure and Strategy
Stephen Willcock, FinancialForce.com, Director of Product Innovation
@stephenwillcock
All about FinancialForce.com
Revolutionizing the Back Office
#1 Accounting, Billing and PSA Apps on the Salesforce platform

▪ Native apps
▪ San Francisco HQ, 595 Market St
▪ R&D in San Francisco, Harrogate UK, and Granada ES
▪ We are hiring! Meet us at Rehab!
Tests and Testability - overview
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Testing strategy
In an ideal world we would test…
An entire system “end-to-end”
Using different types of user
With production data volumes
With complex / varied data profiles
All possible code paths
SIMULTANEOUSLY!
Test pyramid
Fewer tests

More
numerous
tests

More
complex
tests
Less
complex
tests
Test pyramid
Test pyramid
The test pyramid is a concept
developed by Mike Cohn....
[the] essential point is that you
should have many more lowlevel unit tests than high level
end-to-end tests running
through a GUI.
http://martinfowler.com/bliki/TestPyramid.html
Test pyramid
Even with good practices on writing them, end-to-end tests are
more prone to non-determinism problems, which can undermine
trust in them.
In short, tests that run end-to-end through the UI are: brittle,
expensive to write, and time consuming to run.
So the pyramid argues that you should do much more
automated testing through unit tests than you should through
traditional GUI based testing.
http://martinfowler.com/bliki/TestPyramid.html
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Unit test principles - what is a unit?
The smallest testable chunk of code
Independent from other units and systems

Uno
Unit test principles - what is a unit?
Unit test principles - what is a unit?
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Unit test principles - isolation
https://

Uno

Uno

Uno

Uno
@

Uno
Trigger
Validation Rule
Workflow Rule

Uno

Managed Apex

Database
Related Data

…further
dependencies
Unit test principles - isolation
Uno

Uno

Mocked resources
Database
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Unit test principles - saturation
The Force.com platform requires that at least 75% of the Apex
Code in an org be executed via unit tests in order to deploy the
code to production. You shouldn’t consider 75% code coverage to
be an end-goal though
Instead, you should strive to increase the state coverage of
your unit tests
Code has many more possible states than it has lines of code
http://wiki.developerforce.com/page/How_to_Write_Good_Unit_Tests
Unit test principles - saturation
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Unit test principles - expectation
Unit test
•Stakeholder: developers
•Asks: does this code do what it says it will?
System test
•Stakeholder: Business Analyst
•Asks: does this system fulfil my functional requirements?
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Unit test principles - testability
Unit test principles - testability
Well structured, Object Oriented code is likely to be testable:
•Encapsulation - well defined inputs and outputs
•Limited class scope
•Limited class size
•Limited method size
TDD
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
SObject fabrication

Insert supporting
data

Evaluate and
assert

Read data

Insert test data

Database commit

Trigger fires

Triggered record
update

Unit test
SObject fabrication - the unit
trigger OpportunityLineItems on OpportunityLineItem
(before insert) {
for(OpportunityLineItem item : Trigger.new) {
if(item.Description==null)
item.Description = 'foo';
}
}
SObject fabrication - the test

AccountId
StageName
OpportunityLineItem oli = new OpportunityLineItem (
insert new
CloseDate
Description=null );
Opportunity
insert oli;

insert new
oli = [select Description from OpportunityLineItem where
Account
OpportunityId
id=:oli.Id];
UnitPrice
Quantity
insert new
insert new
system.assertEquals('foo',oli.Description);
PricebookEntryId
PricebookEntry
Product2

Select Id from
Pricebook2
SObject fabrication - the test
a = new Account(…); insert a;
o = new Opportunity(…); insert o;
pb = [select Id from Pricebook2 … ];
p = new Product2(…); insert p;
pbe = new PricebookEntry(…); insert pbe;
oli = new OpportunityLineItem(…); insert oli;
oli = [select … from OpportunityLineItem …];
system.assertEquals('foo',oli.Description);
SObject fabrication - the revised unit
trigger OpportunityLineItems on OpportunityLineItem
(before insert) {
new OpportunityLineItemsTriggerHandler().beforeInsert(
Trigger.new );
}

Testable code:
break up the Trigger
SObject fabrication - the revised unit
public class OpportunityLineItemsTriggerHandler {
public void beforeInsert(List<OpportunityLineItem>
items) {
for(OpportunityLineItem item : items) {
if(item.Description==null)
item.Description = 'foo';
}
}
}

Avoid referring to
Trigger variables in the
handler

Testable code:
break up the Trigger
SObject fabrication - the revised test
OpportunityLineItem oli = new OpportunityLineItem(
Description=null );
new OpportunityLineItemsTriggerHandler().beforeInsert(
new List<OpportunityLineItem>{oli});
system.assertEquals('foo',oli.Description);
SObject fabrication #2 - the unit
public class OpportunityService {
public void adjust(OpportunityLineItem oli) {
oli.UnitPrice += (oli.UnitPrice *
oli.Opportunity.Account.Factor__c);
}
}
SObject fabrication #2 - the test
Account a = new Account(Factor__c=0.1);
Opportunity o = new Opportunity(Account=a);
OpportunityLineItem oli = new OpportunityLineItem(
Opportunity=o, UnitPrice=100);
OpportunityService svc = new OpportunityService();
svc.adjust(oli);
system.assertEquals(110,oli.UnitPrice);
SObject fabrication - what did we do?
Structured the code to make it easier to test
•Trigger handler / Trigger
Fabricated SObjects (including relationships)
•In-memory
•No database interaction
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Loose type coupling
Use inheritance to “loosen” a relationship
•Interface
•Superclass
Substitute a mock “sibling implementation” during unit tests
Loose type coupling
Consumer

Provider

OrderController
(Custom
Controller)

Aggregator

OrderController.Item
Unit test
Loose type coupling - the provider (test subject)
public class Aggregator {
List<OrderController.Item> items;
public void setItems(List<OrderController.Item> items) {
this.items = items; }
public Decimal getSum() {
Decimal result = 0;
for(OrderController.Item item : this.items)
result += item.getValue();
return result;
}
}
Loose type coupling - the provider test
List<OrderController.Item> testItems = new
List<OrderController.Item>{ new OrderController.Item(…), … };
Aggregator testAggregator = new Aggregator();
testAggregator.setItems(testItems);
system.assertEquals(123.456,testAggregator.getSum());
Loose type coupling - the revised provider
public class Aggregator {
public interface IItem {
Decimal getValue();
}
public void setItems(List<IItem> items) {…}
Loose type coupling - the revised provider
public Decimal getSum() {
Decimal result = 0;
for(IItem item : items)
result += item.getValue();
return result;
}
}
Loose type coupling - the revised consumer
public controller OrderController {
…
public class Item implements Aggregator.IItem {…}
Aggregator a…
List<Item> items…
a.setItems(items);
Decimal s = a.getSum();
Loose type coupling - the revised provider test
class TItem implements Aggregator.IItem {
Decimal value;
TItem(Decimal d) {
value = d;
}
public getValue() {
return value;
}
}
Loose type coupling - the revised provider test
List<TItem> testItems = new List<TItem>{ new TItem(100),
new TItem(20.006), new TItem(3.45) };
Aggregator testAggregator = new Aggregator();
testAggregator.setItems(testItems);
system.assertEquals(123.456,testAggregator.getSum());
Loose type coupling - what did we do?
OrderController.Item

Aggregator.IItem

TItem

Aggregator

Production

Unit
Test
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Dependency Injection
Dependency Injection is all about injecting dependencies, or
simply said, setting relations between instances
Some people refer to it as being the Hollywood principle "Don't
call me, we'll call you”
I prefer calling it the "bugger" principle: "I don't care who
you are, just do what I ask”
http://www.javaranch.com/journal/200709/dependency-injection-unit-testing.
html
Dependency injection
Consumer

Provider

Opportunity
Controller

Opportunity
Adjuster

Opportunity o;
OpportunityAdjuster a;
a = new OpportunityAdjuster();
a.adjust(o);
Unit test
Dependency injection
Production
usage
Opportunity
Adjuster
Opportunity
Controller

Unit test
usage
Inject
dependency

TOpportunity
Adjuster
Opportunity
Controller
Dependency injection - interface
public interface IAdjustOpportunities {
void adjust(Opportunity o);
}
Dependency injection - provider
public with sharing class OpportunityAdjuster implements
IAdjustOpportunities {
public void adjust(Opportunity o) {
// the actual implementation
// do some stuff to the opp
}
}
Dependency injection - mock provider
public with sharing class TOpportunityAdjuster
implements IAdjustOpportunities {
@testVisible Opportunity opp;
@testVisible Boolean calledAdjust;
public void adjust(Opportunity o) {
opp = o;
calledAdjust = true;
}
}

@testVisible
Dependency injection - consumer
public class OpportunityController {
IAdjustOpportunities adjuster;
@testVisible OpportunityController(
IAdjustOpportunities a,
ApexPages.StandardController c ) {
this.adjuster = a;
…
}
Dependency injection - consumer
public OpportunityController(
ApexPages.StandardController c) {
this(new OpportunityAdjuster(), c);
}
…
public void makeAdjustment() {
adjuster.adjust(opp);
}
Dependency injection - consumer test
Opportunity opp = new Opportunity(…);
ApexPages.StandardController sc = new
ApexPages.StandardController(opp);
TOpportunityAdjuster adjuster = new TOpportunityAdjuster();
OpportunityController oc = new
OpportunityController(adjuster, sc);
oc.makeAdjustment();
system.assert(adjuster.calledAdjust);
system.assertEquals(opp,adjuster.opp);

Constructor
injection
Dependency injection - what did we do?
• Loosen the coupling to a provider class in a consumer class
• Mock the provider class
• Inject the mock provider implementation into the consumer via
a new @testVisible constructor on the consumer class to test
the consumer class
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
SObject Decoupler - the unit
public class OpportunitiesTriggerHandler {
public static void afterUpdate(List<Opportunity> items) {
for(Opportunity item : items) {
if(item.IsClosed){
// do something
}
}
}…
}
SObject Decoupler - the test
@isTest private class OpportunitiesTriggerHandlerTest {
@isTest static void myTest() {
Opportunity o = new Opportunity(IsClosed=true);
OpportunitiesTriggerHandler handler = new
OpportunitiesTriggerHandler();

Field is not writeable:
handler.afterUpdate(new List<Opportunity>{o});
Opportunity.IsClosed
// test something
}

SObject fabrication limitations:
formula fields, rollup summaries,
system fields, subselects
SObject Decoupler - the decoupler
public virtual class OpportunityDecoupler {
public virtual Boolean getIsClosed( Opportunity o ) {
return o.IsClosed;
}
}
SObject Decoupler - the test decoupler
public virtual class TOpportunityDecoupler extends
OpportunityDecoupler {
@testVisible Map<Id,Boolean> IsClosedMap =
new Map<Id,Boolean>();
public override Boolean getIsClosed( Opportunity o ) {
return IsClosedMap.get(o.Id);
}
}
SObject Decoupler - the revised unit
public class OpportunitiesTriggerHandler {
OpportunityDecoupler decoupler;
@testVisible OpportunitiesTriggerHandler(
OpportunityDecoupler od ) {
this.decoupler = od;
}
public OpportunitiesTriggerHandler() {
this(new OpportunityDecoupler());
}

Constructor
injection
SObject Decoupler - the revised unit
public void afterUpdate(List<Opportunity> items) {
for(Opportunity item : items) {
if(decoupler.getIsClosed(item)) {
// do something
}
}
}
}
SObject Decoupler - the revised test
@isTest private class OpportunitiesTriggerHandlerTest {
@isTest static void myTest() {
TOpportunityDecoupler decoupler = new
TOpportunityDecoupler();
Opportunity o = new Opportunity(Id =
TestUtility.getFakeId(Opportunity.SObjectType));
decoupler.IsClosedMap.put(o.Id,true);

Fabrication of
SObject IDs
SObject Decoupler - the revised test
public with sharing class TestUtility {
static Integer s_num = 1;

}

public static String getFakeId(Schema.SObjectType sot) {
String result = String.valueOf(s_num++);
return sot.getDescribe().getKeyPrefix() +
'0'.repeat(12-result.length()) + result;
}

Fabrication of
SObject IDs
SObject Decoupler - the revised test
OpportunitiesTriggerHandler handler = new
OpportunitiesTriggerHandler(decoupler);
handler.afterUpdate(new List<Opportunity>{o});
// test something
}
SObject Decoupler - what did we do?
Mechanism for mocking non-writable SObject properties
•Access the SObject properties via a separate virtual class - the
decoupler
•Decoupler subclass mocks access to non-writable SObject
properties
•Inject the decoupler subclass in the test subject constructor
SObject Decoupler - useful for…
Mocking:
•Formula fields
•System fields
•Rollup summary fields
•Subselects
• Select (Select ... From OpportunityLineItems) From Opportunity
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Expectation
Unit test techniques
• Testability
Adapter - for managed classes
IHandleLockingRules

OpportunitiesTrigger
Handler

LockingRule
Handler
(wrapper)

LockingRules.
LockingRule
Handler
(managed class)

Test
LockingRuleHandler
implements
IHandleLockingRules
Adapter - managed class

global class
LockingRuleHandler
static void handleTrigger()
Adapter - interface
public interface IHandleLockingRules {
void handleAfterUpdate(Map<Id,sObject> oldMap,
Map<Id,sObject> newMap);
}
Adapter - wrapper
public class LockingRuleHandler implements
IHandleLockingRules {
public void handleAfterUpdate(Map<Id,sObject> oldMap,
Map<Id,sObject> newMap) {
LockingRules.LockingRuleHandler.handleTrigger();
}
}
Adapter - mock implementation
public class TLockingRuleHandler implements
IHandleLockingRules {
@testVisible Boolean calledHandleAfterUpdate;
public void handleAfterUpdate(Map<Id,sObject> oldMap,
Map<Id,sObject> newMap) {
calledHandleAfterUpdate = true;
}
}
Adapter - the unit
public class OpportunitiesTriggerHandler {
IHandleLockingRules lockingRules;
@testVisible OpportunitiesTriggerHandler(
IHandleLockingRules lr ) {
this.lockingRules = lr;
}
public OpportunitiesTriggerHandler() {
this(new LockingRuleHandler());
}

Constructor
injection
Adapter - the unit
public void handleAfterUpdate(Map<Id,sObject> oldMap,
Map<Id,sObject> newMap) {
lockingRules.handleAfterUpdate(oldMap, newMap);
}
Adapter - the test
Map<Id,Opportunity> oldMap …
Map<Id,Opportunity> newMap …
TLockingRuleHandler lockingRules = new
TLockingRuleHandler();
OpportunitiesTriggerHandler trig = new
OpportunitiesTriggerHandler(lockingRules);
trig.afterUpdate(oldMap,newMap);
system.assert(lockingRules.calledHandleAfterUpdate);
…
Adapter - what did we do?
Mock a managed class
•Create an interface defining our expectations of the managed
class
•Adapt the the managed class by wrapping and implementing
the interface
•Mock the production class by implementing the same interface
•Inject the mock implementation during unit test execution
In a nutshell…
Unit tests are foundational to an effective Apex testing strategy
Consider testability in the structure / design of your code
Units must be independent to be easily tested
Units can be made independent through fabrication and
substitution of connected resources
Going forward…
Tests and Testability on foobarforce.com
Sample code on Github
@stephenwillcock
Stephen Willcock
Director of Product
Innovation at FinancialForce.
com
@stephenwillcock
Tests and Testability: Apex Structure and Strategy

Tests and Testability: Apex Structure and Strategy

  • 1.
    Tests and Testability ApexStructure and Strategy Stephen Willcock, FinancialForce.com, Director of Product Innovation @stephenwillcock
  • 2.
    All about FinancialForce.com Revolutionizingthe Back Office #1 Accounting, Billing and PSA Apps on the Salesforce platform ▪ Native apps ▪ San Francisco HQ, 595 Market St ▪ R&D in San Francisco, Harrogate UK, and Granada ES ▪ We are hiring! Meet us at Rehab!
  • 3.
    Tests and Testability- overview Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 4.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 5.
    Testing strategy In anideal world we would test… An entire system “end-to-end” Using different types of user With production data volumes With complex / varied data profiles All possible code paths SIMULTANEOUSLY!
  • 6.
  • 7.
  • 8.
    Test pyramid The testpyramid is a concept developed by Mike Cohn.... [the] essential point is that you should have many more lowlevel unit tests than high level end-to-end tests running through a GUI. http://martinfowler.com/bliki/TestPyramid.html
  • 9.
    Test pyramid Even withgood practices on writing them, end-to-end tests are more prone to non-determinism problems, which can undermine trust in them. In short, tests that run end-to-end through the UI are: brittle, expensive to write, and time consuming to run. So the pyramid argues that you should do much more automated testing through unit tests than you should through traditional GUI based testing. http://martinfowler.com/bliki/TestPyramid.html
  • 10.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 11.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 12.
    Unit test principles- what is a unit? The smallest testable chunk of code Independent from other units and systems Uno
  • 13.
    Unit test principles- what is a unit?
  • 14.
    Unit test principles- what is a unit?
  • 15.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 16.
    Unit test principles- isolation https:// Uno Uno Uno Uno @ Uno Trigger Validation Rule Workflow Rule Uno Managed Apex Database Related Data …further dependencies
  • 17.
    Unit test principles- isolation Uno Uno Mocked resources Database
  • 18.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 19.
    Unit test principles- saturation The Force.com platform requires that at least 75% of the Apex Code in an org be executed via unit tests in order to deploy the code to production. You shouldn’t consider 75% code coverage to be an end-goal though Instead, you should strive to increase the state coverage of your unit tests Code has many more possible states than it has lines of code http://wiki.developerforce.com/page/How_to_Write_Good_Unit_Tests
  • 20.
  • 21.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 22.
    Unit test principles- expectation Unit test •Stakeholder: developers •Asks: does this code do what it says it will? System test •Stakeholder: Business Analyst •Asks: does this system fulfil my functional requirements?
  • 23.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 24.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 25.
    Unit test principles- testability
  • 26.
    Unit test principles- testability Well structured, Object Oriented code is likely to be testable: •Encapsulation - well defined inputs and outputs •Limited class scope •Limited class size •Limited method size TDD
  • 27.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 28.
    SObject fabrication Insert supporting data Evaluateand assert Read data Insert test data Database commit Trigger fires Triggered record update Unit test
  • 29.
    SObject fabrication -the unit trigger OpportunityLineItems on OpportunityLineItem (before insert) { for(OpportunityLineItem item : Trigger.new) { if(item.Description==null) item.Description = 'foo'; } }
  • 30.
    SObject fabrication -the test AccountId StageName OpportunityLineItem oli = new OpportunityLineItem ( insert new CloseDate Description=null ); Opportunity insert oli; insert new oli = [select Description from OpportunityLineItem where Account OpportunityId id=:oli.Id]; UnitPrice Quantity insert new insert new system.assertEquals('foo',oli.Description); PricebookEntryId PricebookEntry Product2 Select Id from Pricebook2
  • 31.
    SObject fabrication -the test a = new Account(…); insert a; o = new Opportunity(…); insert o; pb = [select Id from Pricebook2 … ]; p = new Product2(…); insert p; pbe = new PricebookEntry(…); insert pbe; oli = new OpportunityLineItem(…); insert oli; oli = [select … from OpportunityLineItem …]; system.assertEquals('foo',oli.Description);
  • 32.
    SObject fabrication -the revised unit trigger OpportunityLineItems on OpportunityLineItem (before insert) { new OpportunityLineItemsTriggerHandler().beforeInsert( Trigger.new ); } Testable code: break up the Trigger
  • 33.
    SObject fabrication -the revised unit public class OpportunityLineItemsTriggerHandler { public void beforeInsert(List<OpportunityLineItem> items) { for(OpportunityLineItem item : items) { if(item.Description==null) item.Description = 'foo'; } } } Avoid referring to Trigger variables in the handler Testable code: break up the Trigger
  • 34.
    SObject fabrication -the revised test OpportunityLineItem oli = new OpportunityLineItem( Description=null ); new OpportunityLineItemsTriggerHandler().beforeInsert( new List<OpportunityLineItem>{oli}); system.assertEquals('foo',oli.Description);
  • 35.
    SObject fabrication #2- the unit public class OpportunityService { public void adjust(OpportunityLineItem oli) { oli.UnitPrice += (oli.UnitPrice * oli.Opportunity.Account.Factor__c); } }
  • 36.
    SObject fabrication #2- the test Account a = new Account(Factor__c=0.1); Opportunity o = new Opportunity(Account=a); OpportunityLineItem oli = new OpportunityLineItem( Opportunity=o, UnitPrice=100); OpportunityService svc = new OpportunityService(); svc.adjust(oli); system.assertEquals(110,oli.UnitPrice);
  • 37.
    SObject fabrication -what did we do? Structured the code to make it easier to test •Trigger handler / Trigger Fabricated SObjects (including relationships) •In-memory •No database interaction
  • 38.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 39.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 40.
    Loose type coupling Useinheritance to “loosen” a relationship •Interface •Superclass Substitute a mock “sibling implementation” during unit tests
  • 41.
  • 42.
    Loose type coupling- the provider (test subject) public class Aggregator { List<OrderController.Item> items; public void setItems(List<OrderController.Item> items) { this.items = items; } public Decimal getSum() { Decimal result = 0; for(OrderController.Item item : this.items) result += item.getValue(); return result; } }
  • 43.
    Loose type coupling- the provider test List<OrderController.Item> testItems = new List<OrderController.Item>{ new OrderController.Item(…), … }; Aggregator testAggregator = new Aggregator(); testAggregator.setItems(testItems); system.assertEquals(123.456,testAggregator.getSum());
  • 44.
    Loose type coupling- the revised provider public class Aggregator { public interface IItem { Decimal getValue(); } public void setItems(List<IItem> items) {…}
  • 45.
    Loose type coupling- the revised provider public Decimal getSum() { Decimal result = 0; for(IItem item : items) result += item.getValue(); return result; } }
  • 46.
    Loose type coupling- the revised consumer public controller OrderController { … public class Item implements Aggregator.IItem {…} Aggregator a… List<Item> items… a.setItems(items); Decimal s = a.getSum();
  • 47.
    Loose type coupling- the revised provider test class TItem implements Aggregator.IItem { Decimal value; TItem(Decimal d) { value = d; } public getValue() { return value; } }
  • 48.
    Loose type coupling- the revised provider test List<TItem> testItems = new List<TItem>{ new TItem(100), new TItem(20.006), new TItem(3.45) }; Aggregator testAggregator = new Aggregator(); testAggregator.setItems(testItems); system.assertEquals(123.456,testAggregator.getSum());
  • 49.
    Loose type coupling- what did we do? OrderController.Item Aggregator.IItem TItem Aggregator Production Unit Test
  • 50.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 51.
    Dependency Injection Dependency Injectionis all about injecting dependencies, or simply said, setting relations between instances Some people refer to it as being the Hollywood principle "Don't call me, we'll call you” I prefer calling it the "bugger" principle: "I don't care who you are, just do what I ask” http://www.javaranch.com/journal/200709/dependency-injection-unit-testing. html
  • 52.
  • 53.
  • 54.
    Dependency injection -interface public interface IAdjustOpportunities { void adjust(Opportunity o); }
  • 55.
    Dependency injection -provider public with sharing class OpportunityAdjuster implements IAdjustOpportunities { public void adjust(Opportunity o) { // the actual implementation // do some stuff to the opp } }
  • 56.
    Dependency injection -mock provider public with sharing class TOpportunityAdjuster implements IAdjustOpportunities { @testVisible Opportunity opp; @testVisible Boolean calledAdjust; public void adjust(Opportunity o) { opp = o; calledAdjust = true; } } @testVisible
  • 57.
    Dependency injection -consumer public class OpportunityController { IAdjustOpportunities adjuster; @testVisible OpportunityController( IAdjustOpportunities a, ApexPages.StandardController c ) { this.adjuster = a; … }
  • 58.
    Dependency injection -consumer public OpportunityController( ApexPages.StandardController c) { this(new OpportunityAdjuster(), c); } … public void makeAdjustment() { adjuster.adjust(opp); }
  • 59.
    Dependency injection -consumer test Opportunity opp = new Opportunity(…); ApexPages.StandardController sc = new ApexPages.StandardController(opp); TOpportunityAdjuster adjuster = new TOpportunityAdjuster(); OpportunityController oc = new OpportunityController(adjuster, sc); oc.makeAdjustment(); system.assert(adjuster.calledAdjust); system.assertEquals(opp,adjuster.opp); Constructor injection
  • 60.
    Dependency injection -what did we do? • Loosen the coupling to a provider class in a consumer class • Mock the provider class • Inject the mock provider implementation into the consumer via a new @testVisible constructor on the consumer class to test the consumer class
  • 61.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 62.
    SObject Decoupler -the unit public class OpportunitiesTriggerHandler { public static void afterUpdate(List<Opportunity> items) { for(Opportunity item : items) { if(item.IsClosed){ // do something } } }… }
  • 63.
    SObject Decoupler -the test @isTest private class OpportunitiesTriggerHandlerTest { @isTest static void myTest() { Opportunity o = new Opportunity(IsClosed=true); OpportunitiesTriggerHandler handler = new OpportunitiesTriggerHandler(); Field is not writeable: handler.afterUpdate(new List<Opportunity>{o}); Opportunity.IsClosed // test something } SObject fabrication limitations: formula fields, rollup summaries, system fields, subselects
  • 64.
    SObject Decoupler -the decoupler public virtual class OpportunityDecoupler { public virtual Boolean getIsClosed( Opportunity o ) { return o.IsClosed; } }
  • 65.
    SObject Decoupler -the test decoupler public virtual class TOpportunityDecoupler extends OpportunityDecoupler { @testVisible Map<Id,Boolean> IsClosedMap = new Map<Id,Boolean>(); public override Boolean getIsClosed( Opportunity o ) { return IsClosedMap.get(o.Id); } }
  • 66.
    SObject Decoupler -the revised unit public class OpportunitiesTriggerHandler { OpportunityDecoupler decoupler; @testVisible OpportunitiesTriggerHandler( OpportunityDecoupler od ) { this.decoupler = od; } public OpportunitiesTriggerHandler() { this(new OpportunityDecoupler()); } Constructor injection
  • 67.
    SObject Decoupler -the revised unit public void afterUpdate(List<Opportunity> items) { for(Opportunity item : items) { if(decoupler.getIsClosed(item)) { // do something } } } }
  • 68.
    SObject Decoupler -the revised test @isTest private class OpportunitiesTriggerHandlerTest { @isTest static void myTest() { TOpportunityDecoupler decoupler = new TOpportunityDecoupler(); Opportunity o = new Opportunity(Id = TestUtility.getFakeId(Opportunity.SObjectType)); decoupler.IsClosedMap.put(o.Id,true); Fabrication of SObject IDs
  • 69.
    SObject Decoupler -the revised test public with sharing class TestUtility { static Integer s_num = 1; } public static String getFakeId(Schema.SObjectType sot) { String result = String.valueOf(s_num++); return sot.getDescribe().getKeyPrefix() + '0'.repeat(12-result.length()) + result; } Fabrication of SObject IDs
  • 70.
    SObject Decoupler -the revised test OpportunitiesTriggerHandler handler = new OpportunitiesTriggerHandler(decoupler); handler.afterUpdate(new List<Opportunity>{o}); // test something }
  • 71.
    SObject Decoupler -what did we do? Mechanism for mocking non-writable SObject properties •Access the SObject properties via a separate virtual class - the decoupler •Decoupler subclass mocks access to non-writable SObject properties •Inject the decoupler subclass in the test subject constructor
  • 72.
    SObject Decoupler -useful for… Mocking: •Formula fields •System fields •Rollup summary fields •Subselects • Select (Select ... From OpportunityLineItems) From Opportunity
  • 73.
    Tests and Testability Testingstrategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  • 74.
    Adapter - formanaged classes IHandleLockingRules OpportunitiesTrigger Handler LockingRule Handler (wrapper) LockingRules. LockingRule Handler (managed class) Test LockingRuleHandler implements IHandleLockingRules
  • 75.
    Adapter - managedclass global class LockingRuleHandler static void handleTrigger()
  • 76.
    Adapter - interface publicinterface IHandleLockingRules { void handleAfterUpdate(Map<Id,sObject> oldMap, Map<Id,sObject> newMap); }
  • 77.
    Adapter - wrapper publicclass LockingRuleHandler implements IHandleLockingRules { public void handleAfterUpdate(Map<Id,sObject> oldMap, Map<Id,sObject> newMap) { LockingRules.LockingRuleHandler.handleTrigger(); } }
  • 78.
    Adapter - mockimplementation public class TLockingRuleHandler implements IHandleLockingRules { @testVisible Boolean calledHandleAfterUpdate; public void handleAfterUpdate(Map<Id,sObject> oldMap, Map<Id,sObject> newMap) { calledHandleAfterUpdate = true; } }
  • 79.
    Adapter - theunit public class OpportunitiesTriggerHandler { IHandleLockingRules lockingRules; @testVisible OpportunitiesTriggerHandler( IHandleLockingRules lr ) { this.lockingRules = lr; } public OpportunitiesTriggerHandler() { this(new LockingRuleHandler()); } Constructor injection
  • 80.
    Adapter - theunit public void handleAfterUpdate(Map<Id,sObject> oldMap, Map<Id,sObject> newMap) { lockingRules.handleAfterUpdate(oldMap, newMap); }
  • 81.
    Adapter - thetest Map<Id,Opportunity> oldMap … Map<Id,Opportunity> newMap … TLockingRuleHandler lockingRules = new TLockingRuleHandler(); OpportunitiesTriggerHandler trig = new OpportunitiesTriggerHandler(lockingRules); trig.afterUpdate(oldMap,newMap); system.assert(lockingRules.calledHandleAfterUpdate); …
  • 82.
    Adapter - whatdid we do? Mock a managed class •Create an interface defining our expectations of the managed class •Adapt the the managed class by wrapping and implementing the interface •Mock the production class by implementing the same interface •Inject the mock implementation during unit test execution
  • 83.
    In a nutshell… Unittests are foundational to an effective Apex testing strategy Consider testability in the structure / design of your code Units must be independent to be easily tested Units can be made independent through fabrication and substitution of connected resources
  • 84.
    Going forward… Tests andTestability on foobarforce.com Sample code on Github @stephenwillcock
  • 85.
    Stephen Willcock Director ofProduct Innovation at FinancialForce. com @stephenwillcock