Amoss
Amoss - Apex Mock Objects, Spies and Stubs - A Simple Mocking framework for Apex (Salesforce)
Install / Use
/learn @bobalicious/AmossREADME
Amoss
Apex Mock Objects, Spies and Stubs - A Simple Mocking framework for Apex (Salesforce)
Costs, and a note from the author...
Use, replication and extension of Amoss is entirely free, and provided without support (see the associated license, which will take precendence in any ambiguity).
If you wish to donate to the original author and major contributor, you can do so via the original source repository (https://github.com/bobalicious), and using the Sponsorship button on that site.
Please note, there is no obligation to donate, and doing so does not imply any form of contract or support arrangement. I'll always do my best to help though... just reach out to me.
Why use Amoss?
Amoss provides a simple interface for implementing Mock, Test Spy and Stub objects (Test Doubles) for use in Unit Testing.
It's intended to be very straightforward to use, and to result in code that's even more straightforward to read.
As a simple example, the following example:
- Creates a Test Double of the class
DeliveryProvider - Configures the methods
canDeliverandscheduleDeliveryto always returntrue
Amoss_Instance deliveryProviderController = new Amoss_Instance( DeliveryProvider.class );
deliveryProviderController
.when( 'canDeliver' )
.willReturn( true )
.also().when( 'scheduleDelivery' )
.willReturn( true );
DeliveryProvider deliveryProviderDouble = (DeliveryProvider)deliveryProviderController.getDouble();
It also provides an interface for creating and registering an HttpCalloutMock in a simple and easy to read format.
For example:
Amoss_Instance httpCalloutMock = new Amoss_Instance();
httpCalloutMock
.isACalloutMock()
.when()
.method( 'GET' )
.endpoint().containing( 'account/' )
.respondsWith()
.status( 'Complete' )
.statusCode( 200 )
.body( new Map<String,Object>{ 'Name' => 'The account name' } )
.also().when()
.method( 'POST' )
.respondsWith()
.status( 'Not Found' )
.statusCode( 404 );
This documetation page covers the fundamentals of building Test Doubles. For documentation on creating HttpCalloutMocks, see here..
Installating it
git clone / copy / deploy
If you are familar with using SFDX, the ant migration tool or using a local IDE, It is recommended that you either clone this repository or download a release from the release section, copy the Amoss files you require, and install them using your normal mechanism.
Unlocked Package - SFDX
Alternatively, Amoss is available as an Unlocked Package, and the 'currently published' version based this branch can be installed (after setting the default org), using:
sfdx force:package:install --package "amoss@1.2.0-0"
You should note that this may not be the most recent version that exists on this branch. There are times when the most recent version has not been published as an Unlocked Package Version. In addition, the Unlocked Package contains the amoss_main and amoss_test files, though does not include amoss_examples.
Links to the release notes, and any changes pending release can be found at the end of this file.
Note
If you are not familiar with the SFDX commands, then it is recommended that you read the documentation here: https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference_force_package.htm
Unlocked Package - Installation link
For Dev Instances or Production, the Unlocked Package can be installed via:
- https://login.salesforce.com/packaging/installPackage.apexp?p0=04t4K000002SC9NQAW
For all other instances:
- https://test.salesforce.com/packaging/installPackage.apexp?p0=04t4K000002SC9NQAW
Install Button
As a final option, you can install directly from this page, using the following 'Deploy to Salesforce' button.
Note
If running from the branch 'main', you should enter 'main' into the 'Branch/Tag/Commit:' field of 'Salesforce Deploy'.
This is because of a bug in that application that incorrectly selects the default branch as 'master' (https://github.com/afawcett/githubsfdeploy/issues/43)
<a href="https://githubsfdeploy.herokuapp.com"> <img alt="Deploy to Salesforce" src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/src/main/webapp/resources/img/deploy.png"> </a>Be aware that this will grant a third pary Heroku app access to your org. I have no reason to believe that 'Andy in the Cloud' (https://andyinthecloud.com/about/) will do anything malicious with that access, though you should always be aware that there are risks when you grant access to an org, and using such an app is entirely at your own risk.
What do I get?
Amoss consists of 3 sets of classes:
Amoss_- The only parts that are necessary - the framework itself.
AmossTest_- The tests for the framework, and their supporting files. Required if you want to enhance Amoss, not so useful otherwise, but worth keeping if you can, just in case. All
Amoss_classes are defined as@isTest, meaning they do not count towards code coverage or Apex lines of codes.
- The tests for the framework, and their supporting files. Required if you want to enhance Amoss, not so useful otherwise, but worth keeping if you can, just in case. All
AmossExample_- Example classes and tests, showing simple use cases for Amoss in a runnable format. There is no reason to keep these in your code-base once you've read and understand them.
How do I use it?
Constructing and using a Test Double
Amoss can be used to build simple stub objects - AKA Configurable Test Doubles, by:
- Constructing an
Amoss_Instance, passing it the type of the class you want to make a double of. - Asking the resulting 'controller' to generate a Test Double for you.
Amoss_Instance deliveryProviderController = new Amoss_Instance( DeliveryProvider.class );
DeliveryProvider deliveryProviderDouble = (DeliveryProvider)deliveryProviderController.getDouble();
The result is an object that can be used in place of the object being stubbed.
Every non-static public method on the class is available, and when called, will do nothing.
If a method has a return value, then null will be returned.
You then use the Test Double as you would any other instance of the object.
In this example, we're testing scheduleDelivery on the class DeliveryOrder.
The method takes a DeliveryProvider as a parameter, calls methods against it and then returns true when the delivery is successfully scheduled:
Test.startTest();
DeliveryOrder order = new DeliveryOrder().setDeliveryDate( deliveryDate ).setDeliveryPostcode( deliveryPostcode );
Boolean scheduled = order.scheduleDelivery( deliveryProviderDouble );
Test.stopTest();
Assert.isTrue( scheduled, 'scheduleDelivery, when called for a DeliveryOrder that can be delivered, will check the passed provider if it can deliver, and return true if the order can be' );
What we need to do, is configure our Test Double version of DeliveryProvider so that tells the order that it can deliver, and then allows the order to schedule it.
Configuring return values
We can configure the Test Double, so that certain methods return certain values by calling when, method andwillReturn against the controller.
Amoss_Instance deliveryProviderController = new Amoss_Instance( DeliveryProvider.class );
deliveryProviderController
.when( 'canDeliver' )
.willReturn( true )
.also().when( 'scheduleDelivery' )
.willReturn( true );
DeliveryProvider deliveryProviderDouble = (DeliveryProvider)deliveryProviderController.getDouble();
...
Now, whenever either canDeliver or scheduleDelivery are called against our double, true will be returned. This is regardless of what parameters have been passed in.
Responding based on parameters
If we want our Test Double to be a little more strict about the parameters it recieves, we can also specify that methods should return particular values only when certain parameters are passed by using methods like withParameter, thenParameter:
Amoss_Instance deliveryProviderController = new Amoss_Instance( DeliveryProvider.class );
deliveryProviderController
.when( 'canDeliver' )
.withParameter( deliveryPostcode )
.thenParameter( deliveryDate )
.willReturn( true )
.also().when( 'canDeliver' )
.willReturn( false );
DeliveryProvider deliveryProviderDouble = (DeliveryProvider)deliveryProviderController.getDouble();
...
We can also use a 'named parameter' notation, by using the methods withParameterNamed and setTo:
Amoss_Instance deliveryProviderController = new Amoss_Instance( DeliveryProvider.class );
deliveryProviderController
.when( 'canDeliver' )
.withParameterNamed( 'postcode' ).setTo( deliveryPostcode )
.andParameterNamed( 'deliveryDate' ).setTo( deliveryDate )
.willReturn( true )
.also().when( 'canDeliver' )
.willReturn( false );
DeliveryProvider deliveryProviderDouble = (DeliveryProvider)deliveryProviderController.getDouble();
...
If we want to be strict about some parameters, but don't care about others we can also be flexible about the contents of particular parameters, using withAnyParameter, and thenAnyParameter:
Amoss_Instance deliveryProviderController = new Amoss_Instance( DeliveryProvider.class );
deliveryProviderController
.when( 'canDeliver' )
.withParameter( deliveryPostcode )
.thenAnyParameter()
.willReturn( true );
DeliveryProvider deliveryProviderDouble = (DeliveryProvider)deliveryProviderController.getDouble();
...
Or, when using 'named notation', we can simply omit them from our list:
Amoss_Instance deliveryProviderController = new Amoss_Instance( DeliveryProvider.class );
deliveryProviderController
.when( 'canDeliver' )
.withParameterNamed( 'postcode' ).setTo( deliveryPostcode ) // since
