Tests and Testability
Apex Structure and Strategy
Stephen Willcock, FinancialForce.com, Director of Product Innovation
@st...
All about FinancialForce.com
Revolutionizing the Back Office
#1 Accounting, Billing and PSA Apps on the Salesforce platfor...
Tests and Testability - overview
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Satu...
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
Testing strategy
In an ideal world we would test…
An entire system “end-to-end”
Using different types of user
With product...
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 m...
Test pyramid
Even with good practices on writing them, end-to-end tests are
more prone to non-determinism problems, which ...
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
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
• Ex...
Unit test principles - isolation
https://

Uno

Uno

Uno

Uno
@

Uno
Trigger
Validation Rule
Workflow Rule

Uno

Managed A...
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
• Ex...
Unit test principles - saturation
The Force.com platform requires that at least 75% of the Apex
Code in an org be executed...
Unit test principles - saturation
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
Unit test principles - expectation
Unit test
•Stakeholder: developers
•Asks: does this code do what it says it will?
Syste...
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
Unit test principles - testability
Unit test principles - testability
Well structured, Object Oriented code is likely to be testable:
•Encapsulation - well d...
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
SObject fabrication

Insert supporting
data

Evaluate and
assert

Read data

Insert test data

Database commit

Trigger fi...
SObject fabrication - the unit
trigger OpportunityLineItems on OpportunityLineItem
(before insert) {
for(OpportunityLineIt...
SObject fabrication - the test

AccountId
StageName
OpportunityLineItem oli = new OpportunityLineItem (
insert new
CloseDa...
SObject fabrication - the test
a = new Account(…); insert a;
o = new Opportunity(…); insert o;
pb = [select Id from Priceb...
SObject fabrication - the revised unit
trigger OpportunityLineItems on OpportunityLineItem
(before insert) {
new Opportuni...
SObject fabrication - the revised unit
public class OpportunityLineItemsTriggerHandler {
public void beforeInsert(List<Opp...
SObject fabrication - the revised test
OpportunityLineItem oli = new OpportunityLineItem(
Description=null );
new Opportun...
SObject fabrication #2 - the unit
public class OpportunityService {
public void adjust(OpportunityLineItem oli) {
oli.Unit...
SObject fabrication #2 - the test
Account a = new Account(Factor__c=0.1);
Opportunity o = new Opportunity(Account=a);
Oppo...
SObject fabrication - what did we do?
Structured the code to make it easier to test
•Trigger handler / Trigger
Fabricated ...
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
Loose type coupling
Use inheritance to “loosen” a relationship
•Interface
•Superclass
Substitute a mock “sibling implement...
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 ...
Loose type coupling - the provider test
List<OrderController.Item> testItems = new
List<OrderController.Item>{ new OrderCo...
Loose type coupling - the revised provider
public class Aggregator {
public interface IItem {
Decimal getValue();
}
public...
Loose type coupling - the revised provider
public Decimal getSum() {
Decimal result = 0;
for(IItem item : items)
result +=...
Loose type coupling - the revised consumer
public controller OrderController {
…
public class Item implements Aggregator.I...
Loose type coupling - the revised provider test
class TItem implements Aggregator.IItem {
Decimal value;
TItem(Decimal d) ...
Loose type coupling - the revised provider test
List<TItem> testItems = new List<TItem>{ new TItem(100),
new TItem(20.006)...
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
• Ex...
Dependency Injection
Dependency Injection is all about injecting dependencies, or
simply said, setting relations between i...
Dependency injection
Consumer

Provider

Opportunity
Controller

Opportunity
Adjuster

Opportunity o;
OpportunityAdjuster ...
Dependency injection
Production
usage
Opportunity
Adjuster
Opportunity
Controller

Unit test
usage
Inject
dependency

TOpp...
Dependency injection - interface
public interface IAdjustOpportunities {
void adjust(Opportunity o);
}
Dependency injection - provider
public with sharing class OpportunityAdjuster implements
IAdjustOpportunities {
public voi...
Dependency injection - mock provider
public with sharing class TOpportunityAdjuster
implements IAdjustOpportunities {
@tes...
Dependency injection - consumer
public class OpportunityController {
IAdjustOpportunities adjuster;
@testVisible Opportuni...
Dependency injection - consumer
public OpportunityController(
ApexPages.StandardController c) {
this(new OpportunityAdjust...
Dependency injection - consumer test
Opportunity opp = new Opportunity(…);
ApexPages.StandardController sc = new
ApexPages...
Dependency injection - what did we do?
• Loosen the coupling to a provider class in a consumer class
• Mock the provider c...
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
SObject Decoupler - the unit
public class OpportunitiesTriggerHandler {
public static void afterUpdate(List<Opportunity> i...
SObject Decoupler - the test
@isTest private class OpportunitiesTriggerHandlerTest {
@isTest static void myTest() {
Opport...
SObject Decoupler - the decoupler
public virtual class OpportunityDecoupler {
public virtual Boolean getIsClosed( Opportun...
SObject Decoupler - the test decoupler
public virtual class TOpportunityDecoupler extends
OpportunityDecoupler {
@testVisi...
SObject Decoupler - the revised unit
public class OpportunitiesTriggerHandler {
OpportunityDecoupler decoupler;
@testVisib...
SObject Decoupler - the revised unit
public void afterUpdate(List<Opportunity> items) {
for(Opportunity item : items) {
if...
SObject Decoupler - the revised test
@isTest private class OpportunitiesTriggerHandlerTest {
@isTest static void myTest() ...
SObject Decoupler - the revised test
public with sharing class TestUtility {
static Integer s_num = 1;

}

public static S...
SObject Decoupler - the revised test
OpportunitiesTriggerHandler handler = new
OpportunitiesTriggerHandler(decoupler);
han...
SObject Decoupler - what did we do?
Mechanism for mocking non-writable SObject properties
•Access the SObject properties v...
SObject Decoupler - useful for…
Mocking:
•Formula fields
•System fields
•Rollup summary fields
•Subselects
• Select (Selec...
Tests and Testability
Testing strategy
• Test pyramid
Unit test principles
• What is a unit?
• Isolation
• Saturation
• Ex...
Adapter - for managed classes
IHandleLockingRules

OpportunitiesTrigger
Handler

LockingRule
Handler
(wrapper)

LockingRul...
Adapter - managed class

global class
LockingRuleHandler
static void handleTrigger()
Adapter - interface
public interface IHandleLockingRules {
void handleAfterUpdate(Map<Id,sObject> oldMap,
Map<Id,sObject> ...
Adapter - wrapper
public class LockingRuleHandler implements
IHandleLockingRules {
public void handleAfterUpdate(Map<Id,sO...
Adapter - mock implementation
public class TLockingRuleHandler implements
IHandleLockingRules {
@testVisible Boolean calle...
Adapter - the unit
public class OpportunitiesTriggerHandler {
IHandleLockingRules lockingRules;
@testVisible Opportunities...
Adapter - the unit
public void handleAfterUpdate(Map<Id,sObject> oldMap,
Map<Id,sObject> newMap) {
lockingRules.handleAfte...
Adapter - the test
Map<Id,Opportunity> oldMap …
Map<Id,Opportunity> newMap …
TLockingRuleHandler lockingRules = new
TLocki...
Adapter - what did we do?
Mock a managed class
•Create an interface defining our expectations of the managed
class
•Adapt ...
In a nutshell…
Unit tests are foundational to an effective Apex testing strategy
Consider testability in the structure / d...
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
Upcoming SlideShare
Loading in …5
×

Tests and Testability: Apex Structure and Strategy

1,054 views
935 views

Published on

Join us as we look at unit tests in Apex - what they are and where they fit within an efficient and effective testing strategy. We'll also consider the demands that implementing such a strategy makes on how Apex code is structured in a Force.com application. You'll leave with an appreciation of the test pyramid, and some specific examples of mocking techniques.

Published in: Technology, Business
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,054
On SlideShare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
25
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Tests and Testability: Apex Structure and Strategy

  1. 1. Tests and Testability Apex Structure and Strategy Stephen Willcock, FinancialForce.com, Director of Product Innovation @stephenwillcock
  2. 2. 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!
  3. 3. Tests and Testability - overview Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  4. 4. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  5. 5. 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!
  6. 6. Test pyramid Fewer tests More numerous tests More complex tests Less complex tests
  7. 7. Test pyramid
  8. 8. 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
  9. 9. 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
  10. 10. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  11. 11. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  12. 12. Unit test principles - what is a unit? The smallest testable chunk of code Independent from other units and systems Uno
  13. 13. Unit test principles - what is a unit?
  14. 14. Unit test principles - what is a unit?
  15. 15. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  16. 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. 17. Unit test principles - isolation Uno Uno Mocked resources Database
  18. 18. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  19. 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. 20. Unit test principles - saturation
  21. 21. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  22. 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. 23. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  24. 24. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  25. 25. Unit test principles - testability
  26. 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. 27. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  28. 28. SObject fabrication Insert supporting data Evaluate and assert Read data Insert test data Database commit Trigger fires Triggered record update Unit test
  29. 29. SObject fabrication - the unit trigger OpportunityLineItems on OpportunityLineItem (before insert) { for(OpportunityLineItem item : Trigger.new) { if(item.Description==null) item.Description = 'foo'; } }
  30. 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. 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. 32. SObject fabrication - the revised unit trigger OpportunityLineItems on OpportunityLineItem (before insert) { new OpportunityLineItemsTriggerHandler().beforeInsert( Trigger.new ); } Testable code: break up the Trigger
  33. 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. 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. 35. SObject fabrication #2 - the unit public class OpportunityService { public void adjust(OpportunityLineItem oli) { oli.UnitPrice += (oli.UnitPrice * oli.Opportunity.Account.Factor__c); } }
  36. 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. 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. 38. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  39. 39. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  40. 40. Loose type coupling Use inheritance to “loosen” a relationship •Interface •Superclass Substitute a mock “sibling implementation” during unit tests
  41. 41. Loose type coupling Consumer Provider OrderController (Custom Controller) Aggregator OrderController.Item Unit test
  42. 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. 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. 44. Loose type coupling - the revised provider public class Aggregator { public interface IItem { Decimal getValue(); } public void setItems(List<IItem> items) {…}
  45. 45. Loose type coupling - the revised provider public Decimal getSum() { Decimal result = 0; for(IItem item : items) result += item.getValue(); return result; } }
  46. 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. 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. 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. 49. Loose type coupling - what did we do? OrderController.Item Aggregator.IItem TItem Aggregator Production Unit Test
  50. 50. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  51. 51. 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
  52. 52. Dependency injection Consumer Provider Opportunity Controller Opportunity Adjuster Opportunity o; OpportunityAdjuster a; a = new OpportunityAdjuster(); a.adjust(o); Unit test
  53. 53. Dependency injection Production usage Opportunity Adjuster Opportunity Controller Unit test usage Inject dependency TOpportunity Adjuster Opportunity Controller
  54. 54. Dependency injection - interface public interface IAdjustOpportunities { void adjust(Opportunity o); }
  55. 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. 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. 57. Dependency injection - consumer public class OpportunityController { IAdjustOpportunities adjuster; @testVisible OpportunityController( IAdjustOpportunities a, ApexPages.StandardController c ) { this.adjuster = a; … }
  58. 58. Dependency injection - consumer public OpportunityController( ApexPages.StandardController c) { this(new OpportunityAdjuster(), c); } … public void makeAdjustment() { adjuster.adjust(opp); }
  59. 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. 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. 61. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  62. 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. 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. 64. SObject Decoupler - the decoupler public virtual class OpportunityDecoupler { public virtual Boolean getIsClosed( Opportunity o ) { return o.IsClosed; } }
  65. 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. 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. 67. SObject Decoupler - the revised unit public void afterUpdate(List<Opportunity> items) { for(Opportunity item : items) { if(decoupler.getIsClosed(item)) { // do something } } } }
  68. 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. 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. 70. SObject Decoupler - the revised test OpportunitiesTriggerHandler handler = new OpportunitiesTriggerHandler(decoupler); handler.afterUpdate(new List<Opportunity>{o}); // test something }
  71. 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. 72. SObject Decoupler - useful for… Mocking: •Formula fields •System fields •Rollup summary fields •Subselects • Select (Select ... From OpportunityLineItems) From Opportunity
  73. 73. Tests and Testability Testing strategy • Test pyramid Unit test principles • What is a unit? • Isolation • Saturation • Expectation Unit test techniques • Testability
  74. 74. Adapter - for managed classes IHandleLockingRules OpportunitiesTrigger Handler LockingRule Handler (wrapper) LockingRules. LockingRule Handler (managed class) Test LockingRuleHandler implements IHandleLockingRules
  75. 75. Adapter - managed class global class LockingRuleHandler static void handleTrigger()
  76. 76. Adapter - interface public interface IHandleLockingRules { void handleAfterUpdate(Map<Id,sObject> oldMap, Map<Id,sObject> newMap); }
  77. 77. Adapter - wrapper public class LockingRuleHandler implements IHandleLockingRules { public void handleAfterUpdate(Map<Id,sObject> oldMap, Map<Id,sObject> newMap) { LockingRules.LockingRuleHandler.handleTrigger(); } }
  78. 78. Adapter - mock implementation public class TLockingRuleHandler implements IHandleLockingRules { @testVisible Boolean calledHandleAfterUpdate; public void handleAfterUpdate(Map<Id,sObject> oldMap, Map<Id,sObject> newMap) { calledHandleAfterUpdate = true; } }
  79. 79. Adapter - the unit public class OpportunitiesTriggerHandler { IHandleLockingRules lockingRules; @testVisible OpportunitiesTriggerHandler( IHandleLockingRules lr ) { this.lockingRules = lr; } public OpportunitiesTriggerHandler() { this(new LockingRuleHandler()); } Constructor injection
  80. 80. Adapter - the unit public void handleAfterUpdate(Map<Id,sObject> oldMap, Map<Id,sObject> newMap) { lockingRules.handleAfterUpdate(oldMap, newMap); }
  81. 81. 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); …
  82. 82. 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
  83. 83. 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
  84. 84. Going forward… Tests and Testability on foobarforce.com Sample code on Github @stephenwillcock
  85. 85. Stephen Willcock Director of Product Innovation at FinancialForce. com @stephenwillcock

×