Apex Stem · Foundation
v1.0.0·Release notes

ApexTools

Write the scaffolding once, not once per project.Trigger branching, and the callout mock.

Hand-written Trigger.isAfter branching and an HttpCalloutMock implementation class are not things you should be writing once per project. ApexTools is the foundation-layer utility collection that takes them off your hands.

TriggerOppHandler.cls
public with sharing class TriggerOppHandler    extends TriggerHandler {   protected override void afterUpdate(      Map<Id, SObject> newMap, Map<Id, SObject> oldMap) {     // one line to keep only the changed records    Set<Id> needIds = this.getUpdateRecordIdsWithChangedFields(      new List<SObjectField>{ Opportunity.StageName });     (new RegenerateCollection(needIds)).invoke();  }}// No Trigger.isAfter. No new/old comparison.

Override only the hooks you need, out of the seven. The moment you write extends TriggerHandler, deciding the trigger context stops being your job.

What is in the box

A utility collection for the foundation layer. What is in it today isthe two things every project ended up rewriting. More will follow whenever something of the same nature turns up.

Pillar 1

The TriggerHandler base class

It holds the seven hooks (beforeInsert throughafterUndelete, plusandFinally) asprotected virtual. Override only what you need, andhand-written `Trigger.isAfter && Trigger.isInsert` branching disappears. Four helpers that return "only the records where these fields changed" come with it.

Read about the seven hooks and the change detection helpers →
Pillar 2

IHttpRequestHandler

It makes callouts replaceable through DI:HttpRequestHandler in production,MockHttpRequestHandler in tests. Responses are assembled purely by declaring MockResponse, soeven an ordered sequence needs no `HttpCalloutMock` implementation class.

Read about the two modes and the Spy →
The scaffolding you rewrite every time

Two features with nothing in common.Except where they came from.

Trigger branching and HTTP mocking have nothing to do with each other as features. What they share is where they came from: the platform never shipped them, sosomebody rewrote them on every project. And each rewrite came out a little different, so the same review discussion happened again and again.

ApexTools is the place where that gets written once and settled. The bar for inclusion is exactly that origin (the platform did not ship it, and you rewrote it every time), somore will be added whenever something clears it. Two of them are in so far.

How it reads

For each of the two pillars, here is the code you kept rewriting, next to what replaces it.

Pillar 1 · narrowing to "only the records where this field changed"
TriggerOppHandler.clsHand-written
public void execute() {  if (Trigger.isAfter && Trigger.isUpdate) {    Set<Id> needIds = new Set<Id>();    for (Opportunity o : (List<Opportunity>) Trigger.new) {      Opportunity old =        (Opportunity) Trigger.oldMap.get(o.Id);      if (o.StageName != old.StageName) {        needIds.add(o.Id);      }    }    (new RegenerateCollection(needIds)).invoke();  } else if (Trigger.isAfter && Trigger.isInsert) {    // ...  }}
TriggerOppHandler.clsApexTools
protected override void afterUpdate(    Map<Id, SObject> newMap, Map<Id, SObject> oldMap) {   Set<Id> needIds =    this.getUpdateRecordIdsWithChangedFields(      new List<SObjectField>{ Opportunity.StageName });   (new RegenerateCollection(needIds)).invoke();}
Pillar 2 · mocking the three moves "check, create, fetch again"
MyCalloutMock.clsHand-written
public class MyCalloutMock    implements HttpCalloutMock {  private Integer callCount = 0;   public HttpResponse respond(HttpRequest req) {    HttpResponse res = new HttpResponse();    callCount++;    if (callCount == 1) {      res.setStatusCode(404);      res.setBody('{"message":"not found"}');    } else if (callCount == 2) {      res.setStatusCode(201);      res.setBody('{"id":"abc"}');    } else {      res.setStatusCode(200);      res.setBody('{"id":"abc","name":"Acme"}');    }    return res;  }}
KintoneUpsert_T.clsApexTools
MockHttpRequestHandler mock =  new MockHttpRequestHandler(new List<MockResponse>{    MockResponse.of('GET').respond(notFound, 404),    MockResponse.of('POST').respond(created, 201),    MockResponse.of('GET').respond(found, 200)  }); new KintoneUpsertUsecase(input, mock).invoke(); Assert.areEqual(1, mock.countByMethod('POST'));

What disappears in both cases is code that manages state by hand. On the left it is the Trigger context check in one case and the callCount counter in the other. On the right both have become declarations: the hook name carries the timing, the list order carries the response sequence.

Position in Apex Stem

ApexTools is the Foundation member of the four OSS libraries that make upApex Stem. It underpins the Handler layer itself in theHandler-Usecase Architecture: where the other three work inside the Usecase, ApexTools sits outside it.

ApexEloquent
Data Access: SOQL / DML and mocking
ApexBlueprint
Test Data Factory: integration-test data for real DML
ApexTrace
Lifecycle Logging: tracing Usecase paths and verifying them in tests
ApexTools
Foundation: the TriggerHandler base class and an HTTP DI wrapper
Related Documents

Start with the Developer Guide

The seven TriggerHandler hooks and the two IHttpRequestHandler modes, followed through in real code.