0

I've created an Apex utility (invocable) which takes a record Id and searches for existing paused/waiting Flow interviews. That is, given a RelatedRecordId, it queries for FlowRecordRelation records, with optional Parent.CurrentElement and Parent.InterviewLabel params.

Is there any way to write a test for this that doesn't involve creating a dummy "test" Flow in the org? I need to be able to create FlowInterview & FlowRecordRelation records, but they can't be tied to a specific Flow definition which may or may not always exist in the org...

EDIT: The relevant code:

public without sharing class FindFlowRecordRelations_Invocable {

    public class RequestInfo {

        @InvocableVariable(Required=true Label='Record Id')
        public Id recordId;

        @InvocableVariable(Label='Current Element Name')
        public String currentElement;

        @InvocableVariable(Label='Interview Label (starts with)')
        public String interviewLabel;

    }

    @InvocableMethod(Label='Find FlowRecordRelations')
    public static List<ResponseInfo> processRequests(List<RequestInfo> requests) {

        List<FlowRecordRelation> flowRecordRelations = getFlowRecordRelations(requests);
        Map<Id,List<FlowRecordRelation>> flowRelationsByRecordIdMap = getFlowRelationsByRecordId(flowRecordRelations);

        List<ResponseInfo> responses = new List<ResponseInfo>();

        for(RequestInfo request : requests) {
            responses.add(new ResponseInfo(request, flowRelationsByRecordIdMap));
        }

        return responses;
    }

    private static List<FlowRecordRelation> getFlowRecordRelations(List<RequestInfo> requests) {...}

    private static Map<Id,List<FlowRecordRelation>> getFlowRelationsByRecordId(List<FlowRecordRelation> flowRecordRelations) {...}

    public class ResponseInfo {

        public RequestInfo request { get; private set; }

        @InvocableVariable(Label='Flow Record Relations')
        public List<FlowRecordRelation> flowRecordRelations;

        public ResponseInfo(RequestInfo req, Map<Id,List<FlowRecordRelation>> flowRelationsByRecordIdMap) {
            this.request = req;

            if(flowRelationsByRecordIdMap.containsKey(req.recordId)) {
                this.flowRecordRelations = flowRelationsByRecordIdMap.get(req.recordId);
            }
            else {
                System.debug('No existing FlowRecordRelations matching the query parameters were found for' +
                         ' record with Id: '+req.recordId);
            }
        }
    }
}
  • 1
    That sounds like a use case for dependency injection. – David Reed Sep 11 '18 at 19:55
  • @DavidReed Is there any version of this that's available in v43 of the API? Need to deploy this week... – Mike Sep 11 '18 at 20:11
  • It's more of a development and testing methodology than an API as such. It's hard to be more specific without knowing more about the concrete code, but using DI can require some structural changes. Essentially, you delegate your querying to a class that can be "injected" against in test context to return some fixed set of synthetic results. – David Reed Sep 11 '18 at 20:44
  • @DavidReed Thanks - I guess I just don't know how to implement that. What would I need to provide in my initial post to help with advising how to use dependency injection? (As for my API reference, I had been under the impression that Winter 19 added further ability to implement DI, so I wanted to specify that I needed this in a Summer 18 org on v43) – Mike Sep 12 '18 at 13:31
  • Apologies if I've been a little cagey; I don't know much about working with Flow interviews in Apex, so I haven't written up an answer yet. I will do so this afternoon if your question doesn't get more attention from someone who's worked on that species of problem before. Could you edit some code into your question to show at least the skeleton of the logic that you need to build a test for? – David Reed Sep 12 '18 at 13:35
1

I do think dependency injection is the way to go here. I'm assuming that the @InvocableMethod processRequests() calls getFlowRecordRelations(), which then performs a SOQL query against FlowRecordRelation.

That's the dependency you have to inject, and one way to do it looks something like this:

public without sharing class FindFlowRecordRelations_Invocable {

    public interface FlowRelationQueryDelegate {

        List<FlowRecordRelation> getFlowRecordRelations(List<RequestInfo> requests);

        Map<Id,List<FlowRecordRelation>> getFlowRelationsByRecordId(List<FlowRecordRelation> flowRecordRelations);
       // n.b. if this just calls through to getFlowRecordRelations() 
       // and does some logic, it may not belong in the delegate.

    }

    @TestVisible
    private class RealFlowRelationQueryDelegate implements FlowRelationQueryDelegate {
        // Actual method implementation goes here.
        // Limit code in the delegate to actual queries as much as possible -
        // move all dependent logic outside the delegate so it can be tested with mock data.
    }

    @TestVisible 
    private static FlowRelationQueryDelegate delegate;

    @InvocableMethod(Label='Find FlowRecordRelations')
    public static List<ResponseInfo> processRequests(List<RequestInfo> requests) {
        // Delegate all querying into the delegate class.
        if (delegate == null) delegate = new RealFlowRelationQueryDelegate();
    }
}

Now, in the test class, we implement our own delegate implementing FindFlowRecordRelations_Invocable.FlowRelationQueryDelegate:

@isTest
public class FindFlowRecordRelations_InvocableTEST {
    public class MockDelegate implements FindFlowRecordRelations_Invocable.FlowRelationQueryDelegate {
        public List<FlowRecordRelation> getFlowRecordRelations(List<RequestInfo> requests) {
            // Here, we synthesize `FlowRecordRelation` (and/or 
            // `FlowInterview`, if needed) records and return them 
            // to exercise the functionality under test.
            // if you can't construct records that "look" real, you can 
            // often route around it with JSON deserialization
        }

        Map<Id,List<FlowRecordRelation>> getFlowRelationsByRecordId(List<FlowRecordRelation> flowRecordRelations) {
            // Implementation here (but see note above)
        }
    }

@isTest
public static void should_process_relations() {
    // Create a new delegate and assign it to `FindFlowRecordRelations_Invocable.delegate`.
    // Then, exercise the functionality, and make assertions.
}
  • Thanks! I'll review this and see what I can manage to implement. In the meantime, I've added the processRequests() in case it helps. – Mike Sep 13 '18 at 16:15
  • Yep, that additional code confirms in my mind at least that the delegate (and hence the test mock) only needs to implement getFlowRecordRelations(). – David Reed Sep 13 '18 at 16:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.