ZCMock is a development utility to build unit tests for zAgileConnect API. ZCMock can instance stubbed objects of zAgileConnect API classes

ZCMock Api classes

ZCMock

Utility to create mocks of a Class

Interface

interface ZCMock extends StubProvider{
    Object mock(Type ty);
    Object mock(Type ty, StubProvider sp);
    ZCMockMethodRecorder when(Object input);
    ZCMockMethodRecorder when();
    void startStubbing();
    void stopStubbing();
    Boolean verify(Object stubbedObject, String methodName, List<Type> listOfTypes, List<Object> listOfArgs);
    Object any();
    List<Object> anyList();
    Set<Object> anySet();
    Map<Object,Object> anyMap();
    Blob anyBlob();
    Boolean anyBoolean();
    Date anyDate();
    Datetime anyDatetime();
    Decimal anyDecimal();
    Double anyDouble();
    Id anyId();
    Integer anyInteger();
    Long anyLong();
    String anyString();
    Object eq(Object obj);
    List<Object> eqList(List<Object> lst);
    Set<Object> eqSet(Set<Object> s);
    Map<Object,Object> eqMap(Map<Object, Object> m);
    Blob eqBlob(Blob b);
    Boolean eqBoolean(Boolean b);
    Date eqDate(Date d);
    Datetime eqDatetime(Datetime dt);
    Decimal eqDecimal(Decimal d);
    Double eqDouble(Double d);
    Id eqId(Id i);
    Integer eqInteger(Integer i);
    Long eqLong(Long lo);
    String eqString(String str);
}
CODE

ZCMockRecorder

Utility to record outputs of a method for a stubbed object.

Interface

public interface ZCMockMethodRecorder {
    ZCMockMethodRecorder thenReturn(Object result);
    ZCMockMethodRecorder thenException(Exception ex);
}
CODE

Basic steps of usage

  1. Create an instance of ZCMock

  2. Create a mock using ZCMock instance

  3. Inject mock to an object to test

  4. Stubbing mock methods

  5. Start test

  6. Validate test

1. Create an instance of ZCMock

zsfjira.ZCMock zcMock = new zsfjira.ZCMock();
CODE

2. Create a mock using ZCMock instance

zsfjira.ZCIssueApi zcIssueApi = (zsfjira.ZCIssueApi)zcMock.mock(zsfjira.ZCIssueApi.class);
CODE

3. Inject mock to an object to test

zsfjira.ZC.Issues = zcIssueApi;
CODE

4. Stubbing mock methods

String testIssueKey = 'TP-11';
zsfjira.ZCBeans.IssueTemplate issueTemplate = new zsfjira.ZCBeans.IssueTemplate(testIssueKey);

zcMock.startStubbing();
zcMock.when(zsfjira.ZC.Issues.createIssue(issueTemplate))
        .thenReturn(new zsfjira.ZCBeans.IssueUpsertResult(issueTemplate));
zcMock.stopStubbing();
CODE

5. Start test

zsfjira.ZCBeans.IssueUpsertResult result = zsfjira.ZC.Issues.createIssue(issueTemplate);
CODE

6. Validate test

System.assertEquals(testIssueKey, result.getIssueKey());
CODE

Features

Any/Eq match parameters

ZCMock instances Stubbed objects of a given class, then you can define outputs for an exact input by using "eq", for cases where you don’t know the exact input you can use the "any" parameter

If you use one ZCMock::any method parameter you must use ZCMock::eq or ZCMock::any for the remained parameters

Sample

zsfjira.ZC.Issues.createJIRAIssueComment(zcMock.eqString('TP-11'), zcMock.anyString())
CODE
Stubbed Objectzsfjira.ZC.Issues
Stubbed Object MethodcreateJIRAIssueComment
Any parameterzcMock.anyString()
Eq parameterzcMock.eqString('TP-11')


Sample

This sample mocks ZCApi::createJIRAIssueComment method. The issue key is known to be "TP-11" but the comment value can be any value.

@IsTest
public with sharing class client_ZCMockTest_any {

    @IsTest
    static void testAnyArguments(){
        zsfjira.ZCMock zcMock = new zsfjira.ZCMock();

        zsfjira.ZC.Issues = (zsfjira.ZC.IssueApi) zcMock.mock(zsfjira.ZCIssueApi.class);

        zsfjira.ZCBeans.IssueCommentResult commentResult = new zsfjira.ZCBeans.IssueCommentResult('TP-11', 'commentId');

        zcMock.startStubbing();
        // use of eq is mandatory because we are using an any
        zcMock.when(zsfjira.ZC.Issues.createJIRAIssueComment(zcMock.eqString('TP-11'), zcMock.anyString())).thenReturn(commentResult);
        zcMock.stopStubbing();

        System.assertEquals(zsfjira.ZC.Issues.createJIRAIssueComment('TP-11', 'comment content'), commentResult);
    }
}
CODE

Verify method was invoked

After invoking a function, you may want to validate if one or many inner functions were called among the first invocation, use ZCMock::verify in this scenario.

Sample

This sample mocks ZCApi::createJIRAIssueComment method and asserts if the method was invoked by using ZCMock::verify

@IsTest
public with sharing class client_ZCMockTest_verify {

    @IsTest
    static void testVerify(){
        zsfjira.ZCMock zcMock = new zsfjira.ZCMock();
        zsfjira.ZC.Issues = (zsfjira.ZC.IssueApi) zcMock.mock(zsfjira.ZCIssueApi.class);

        zcMock.startStubbing();
        // use of eq is mandatory because we are using an any
        zcMock.when(zsfjira.ZC.Issues.createJIRAIssueComment(zcMock.eqString('TP-11'), zcMock.anyString()))
                .thenReturn(new zsfjira.ZCBeans.IssueCommentResult('', ''));
        zcMock.stopStubbing();

        System.assert(!zcMock.verify(zsfjira.ZC.Issues, 'createJIRAIssueComment', new List<Type>{String.class, String.class}, new List<Object>{'TP-11', zcMock.anyString()}));
        zsfjira.ZC.Issues.createJIRAIssueComment('TP-11', 'comment content');
        System.assert(zcMock.verify(zsfjira.ZC.Issues, 'createJIRAIssueComment', new List<Type>{String.class, String.class}, new List<Object>{'TP-11', zcMock.anyString()}));
    }

}


CODE

Testing Void Methods

If a Method doesn’t return a value(void) It isn’t required to mock it. you can verify the method was executed by using ZCMock::verify

Sample

The method ZCApi::postErrorMessage returns void, then it can be tested as following:

@IsTest
public with sharing class client_ZCMockTest_verifyVoidMethod {

    @IsTest
    static void testVerifyVoidMethod(){
        zsfjira.ZCMock zcMock = new zsfjira.ZCMock();
        zsfjira.ZC.Issues = (zsfjira.ZC.IssueApi) zcMock.mock(zsfjira.ZCIssueApi.class);

        Case ca = new Case();
        insert ca;

        System.assert(!zcMock.verify(zsfjira.ZC.Issues, 'postErrorMessages',
                new List<Type>{List<zsfjira.ZCBeans.GenericErrorMessage>.class}, new List<Object>{zcMock.any()}));

        zsfjira.ZC.Issues.postErrorMessages(new List<zsfjira.ZCBeans.GenericErrorMessage>{
                new zsfjira.ZCBeans.GenericErrorMessage(ca.Id, 'a message')
        });

        System.assert(zcMock.verify(zsfjira.ZC.Issues, 'postErrorMessages',
                new List<Type>{List<zsfjira.ZCBeans.GenericErrorMessage>.class}, new List<Object>{zcMock.any()}));

    }
}


CODE

Custom stub provider

ZCMock is used for creating Stubbed Objects only. If you have a use case isn’t covered by ZCMock, then, It is possible to build unit tests with a Custom Stub Provider. Use a Custom Stub Provider to return mock responses.

Sample

In this example, for any method calles for ZC.Issues the return value will be force to return a issue upsert result. refer to https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_interface_System_StubProvider.htm for further details about the stub provider interface.

@IsTest
public with sharing class client_ZCMockTest_customStubProvider {

    static String testIssueKey = 'TP-11';

    @IsTest
    static void testCustomStubProvider(){
        zsfjira.ZCMock zcMock = new zsfjira.ZCMock();

        zsfjira.ZC.Issues = (zsfjira.ZC.IssueApi)zcMock.mock(zsfjira.ZCIssueApi.class,
                        new CustomStubProvider());

        zsfjira.ZCBeans.IssueUpsertResult result = zsfjira.ZC.Issues.createIssue(null);

        System.assertEquals(testIssueKey, result.getIssueKey());
    }

     class CustomStubProvider implements StubProvider{
        public Object handleMethodCall(Object stubbedObject, String stubbedMethodName, Type returnType,
                List<Type> listOfParamTypes, List<String> listOfParamNames, List<Object> listOfArgs) {
            return new zsfjira.ZCBeans.IssueUpsertResult(new zsfjira.ZCBeans.IssueTemplate(testIssueKey));
        }
    }
}
CODE


Trigger example: On Case Close then Close Issue

The sample closes a Jira Issue when a linked SF Case is closed. If an error is raised when closing the Issue it will be posted on the Case.

The test cover Success and Failure cases

trigger client_OnCaseClosedJiraIssuesTrigger on Case (after update) {
    List<Case> cases = new List<Case>();

    for(Case oldCase: Trigger.old){
        Case newCase = Trigger.newMap.get(oldCase.Id);
        if(oldCase.Status != newCase.Status && newCase.Status == 'Closed'){
            cases.add(oldCase);
        }
    }

    for(Case ca: cases){
        for ( zsfjira__ZIssue_SF__c issueSf :[SELECT zsfjira__ZIssue__r.zsfjira__IssueKey__c FROM zsfjira__ZIssue_SF__c WHERE zsfjira__Case__c = :ca.Id] ){
            client_CaseClosedJiraIssuesHandler.closeIssue(ca.Id, issueSf.zsfjira__ZIssue__r.zsfjira__IssueKey__c);
        }
    }
}
CODE
public class client_CaseClosedJiraIssuesHandler {

    @Future (Callout = true)
    public static void closeIssue(Id caseId, String issueKey){
        zsfjira.ZCBeans.AvailableTransitionsResult availableTransitions = zsfjira.ZC.Issues.getAvailableTransitions(issueKey);

        if(availableTransitions.getAvailableTransitionsNames().contains('Close Issue')){
            zsfjira.ZCBeans.TransitionIssueBean transitionIssueBean = availableTransitions.buildIssueTransitionFromName('Close Issue');
            transitionIssueBean.setFieldByName('Resolution', new Map<String, String>{'name' => 'Done'});
            transitionIssueBean.setComment('Closed by case closed');
            zsfjira.ZCBeans.TransitionIssueResult transitionIssueResult = zsfjira.ZC.Issues.transitionIssue(issueKey, transitionIssueBean);

            if(transitionIssueResult.hasError()){
                zsfjira.ZCBeans.GenericErrorMessage errorMessage =
                        new zsfjira.ZCBeans.GenericErrorMessage(caseId, transitionIssueResult.getErrorMessage());
                zsfjira.ZC.Issues.postErrorMessages(new List<zsfjira.ZCBeans.GenericErrorMessage>{errorMessage});
            }

        }
    }
}
CODE
@IsTest
public class client_CaseClosedJiraIssuesHandlerTest {

    private static String testCaseSubject = 'Test Case';
    private static Integer testIssueId = 1001;
    private static String testIssueKey = 'TP-11';

    @TestSetup
    static void setup(){
        Case ca = new Case(Origin = 'Email', Subject = testCaseSubject);
        insert ca;

        zsfjira__ZIssue__c zIssue = new zsfjira__ZIssue__c(
                zsfjira__IssueId__c = testIssueId,
                zsfjira__IssueKey__c = testIssueKey
        );
        insert zIssue;

        zsfjira__ZIssue_SF__c zIssueSF = new zsfjira__ZIssue_SF__c(
                zsfjira__ZIssue__c = zIssue.Id,
                zsfjira__Case__c = ca.Id
        );
        insert zIssueSF;
    }

    static zsfjira.ZCMock configZCMock(){
        zsfjira.ZCMock zcMock = new zsfjira.ZCMock();

        zsfjira.ZC.Issues = (zsfjira.ZCIssueApi)zcMock.mock(zsfjira.ZCIssueApi.class);
        zsfjira.ZC.IssueFactory = (zsfjira.ZCIssueFactory)zcMock.mock(zsfjira.ZCIssueFactory.class);

        return zcMock;
    }

    @IsTest
    static void transitionIssueTest(){
        zsfjira.ZCMock zcMock = configZCMock();

        String availableTransitionsString = '{"expand": "transitions", "transitions": [{"name":  "Open"}, {"name": "Close Issue", "id": 10, "fields": {"resolution": {"name": "Resolution"}}}]}';

        zsfjira.ZCBeans.AvailableTransitionsResult availableTransitions = new zsfjira.ZCBeans.AvailableTransitionsResult(
                (Map<String, Object>)JSON.deserializeUntyped(availableTransitionsString)
                ,null
        );
        zsfjira.ZCBeans.TransitionIssueResult transitionIssueResult = new zsfjira.ZCBeans.TransitionIssueResult(testIssueKey, null);

        zcMock.startStubbing();
        zcMock.when(zsfjira.ZC.Issues.getAvailableTransitions(zcMock.anyString()))
                .thenReturn(availableTransitions);
        zcMock.when(zsfjira.ZC.Issues.transitionIssue(zcMock.anyString(), (zsfjira.ZCBeans.TransitionIssueBean)zcMock.any()))
                .thenReturn(transitionIssueResult);
        zcMock.stopStubbing();

        Case ca = [SELECT Id FROM Case WHERE Subject=:testCaseSubject LIMIT 1];

        Test.startTest();
        ca.Status = 'Closed';
        update ca;
        Test.stopTest();
    }
}
CODE