8

We have code that will submit an Opportunity for approval when saved at a certain stage. When I do this manually in the UI I see the Opportunity is submitted for approval. I can query the ProcessInstance, ProcessInstanceSteps and ProcessInstanceWorkitems tables and see all the items created using the "Force.com Explorer (beta)".

I have unit test code that creates an Opportunity, updates a custom field to Stage 2 which should submit it for approval. I can query and see everything other than the ProcessInstanceWorkitems created, without these I can't reject or approve the Opportunity to verify the other actions fire correctly. Should I be able to process an Approval in a unit test? For a change I'm actually hoping I've coded something wrong.

Test code

//Code to create an Opportunity o comes here, then
insert(o); 

// Make sure this isn't already queued up for approval before saving as a Stage 2
List<ProcessInstance> processInstances = [select Id, Status from ProcessInstance where TargetObjectId = :o.id];
System.assertEquals(processInstances.size(),0,'Approval Process already triggered when saving at Stage 1');

o.Process_Stage__c = TestConstants.STAGE_2; 
update(o);

// Assert an approval process has been initiated
processInstances = [select Id, Status from ProcessInstance where TargetObjectId = :o.id];
System.assertEquals(processInstances.size(),1, 'Only 1 approval processes should be created when saving at Stage 2, found '+processInstances.size());

List<ProcessInstanceStep> processInstanceSteps = [SELECT ActorId, OriginalActorId, StepStatus 
    FROM ProcessInstanceStep 
    WHERE ProcessInstanceId = :processInstances[0].id];
System.assert(processInstanceSteps.size() > 0,'No approval processes steps found when saving at Stage 2');

Everything is fine until this point. I now want to create a ProcessWorkItemRequest and process the Workitems to reject/approve the Opportunity and assert it's done what I expect. However the statement below (and all the commented out versions I've run) indicate there are no ProcessInstanceWorkitems at all.

//List<ProcessInstanceWorkitem> processInstanceWorkitems = [SELECT ActorId, CreatedById, Id FROM ProcessInstanceWorkitem WHERE ProcessInstanceId = :processInstances[0].id];
//List<ProcessInstanceWorkitem> processInstanceWorkitems = [Select p.Id from ProcessInstanceWorkitem p where p.ProcessInstance.TargetObjectId = :o.id];
//List<ProcessInstance> processInstancesWithWorkitems = [SELECT Id, (SELECT Id, ActorId, ProcessInstanceId FROM Workitems) FROM ProcessInstance WHERE TargetObjectId = :o.id];
List<ProcessInstanceWorkitem> processInstanceWorkitems = [SELECT ActorId, CreatedById, Id FROM ProcessInstanceWorkitem];
System.assert(processInstanceWorkitems.size() > 0,'No approval processes Workitems found when saving at Stage 2');

Any offers to what I'm missing or what I've done wrong? My list of processInstanceWorkitems is always zero size.

3
  • 1
    A workaround is for your test class to simulate doing the field updates that your approve/reject actions would cause to happen. This way you can avoid the need to access ProcessInstanceWorkItem. Hypothesis: ProcessInstanceWorkItem may be created async to your testmethod and thus not available until after Test.stopTest() – cropredy Oct 10 '13 at 21:27
  • Thanks. That would indeed workaround having to process the non-existant ProcessInstanceWorkitems. However it wouldn't let me test whether the Approval Process performed the correct field updates as I'd be performing them manually in the test code though. I was writing this test so I could assert the Approval Process itself performed the correct updates and sent the Opportunity to the correct approver(s). We have a complex set of Approval Processes based on many fields on the Opportunity. If the ProcessInstanceWorkitem are created asynchronously then I fear I'm at a dead end, will investigate.. – Andrew Oct 11 '13 at 9:40
  • 1
    Can you please confirm that the part of apex code that is submitting opportunity for approval is not written in future method or batch apex. If it is so, Can you please give it a try by putting the opportunity update part in test class within Test.startTest() and Test.stopTest() ? Thanks, Shankar – S.Sharma Oct 11 '13 at 17:23
5

Andrew, The SFDC doc is maddeningly vague on this but the example in the Apex doc does what you need.

The line Approval.ProcessResult result = Approval.process(req1); will submit your record to the Approval Process engine that in turn will select a qualifying approval process. The object result can be used to fetch the workItems and then you can simulate approval/reject actions that the Approval Process engine will then execute. Then you can query your database to see if the approval processes as built in the force.com UI are functioning as expected.

public class TestApproval {
void submitAndProcessApprovalRequest() {
    // Insert an account
    Account a = new Account(Name='Test',annualRevenue=100.0);

    insert a;

    // Create an approval request for the account
    Approval.ProcessSubmitRequest req1 = 
        new Approval.ProcessSubmitRequest();
    req1.setComments('Submitting request for approval.');
    req1.setObjectId(a.id);

    // Submit the approval request for the account
    Approval.ProcessResult result = Approval.process(req1);

    // Verify the result
    System.assert(result.isSuccess());

    System.assertEquals(
        'Pending', result.getInstanceStatus(), 
        'Instance Status'+result.getInstanceStatus());

    // Approve the submitted request
    // First, get the ID of the newly created item
    List<Id> newWorkItemIds = result.getNewWorkitemIds();

    // Instantiate the new ProcessWorkitemRequest object and populate it
    Approval.ProcessWorkitemRequest req2 = 
        new Approval.ProcessWorkitemRequest();
    req2.setComments('Approving request.');
    req2.setAction('Approve');
    req2.setNextApproverIds(new Id[] {UserInfo.getUserId()});

    // Use the ID from the newly created item to specify the item to be worked
    req2.setWorkitemId(newWorkItemIds.get(0));

    // Submit the request for approval
    Approval.ProcessResult result2 =  Approval.process(req2);

    // Verify the results
    System.assert(result2.isSuccess(), 'Result Status:'+result2.isSuccess());

    System.assertEquals(
        'Approved', result2.getInstanceStatus(), 
        'Instance Status'+result2.getInstanceStatus());
}
}
2
  • Thanks, the documentation does seem to be a little thin on details just where I'd like them! I'd used that code already to get me started (salesforce.com/us/developer/docs/apexcode/…). Unfortunately the code which is submitting the Opportunity for approval is in the Opportunity Trigger, from my test I don't have access to the ProcessResult object containing the new ProcessInstanceWorkitem Ids. This is why I trying to read them from the ProcessInstanceWorkitems table. – Andrew Oct 14 '13 at 16:03
  • @Andrew - yes, I thought about your point but the above is still worth trying using the object id of the Opportunity record. It would seem as if the Opportunity would end up in two approval process instances - one when your trigger executes and one when you simulate manually submitting the record for approval. Since the record gets locked, I'm not sure what happens when you call Approval.process(myOppoId) in the test method. If this is a roadblock, you could add a new condition to your approval process entry criteria to avoid entry when trigger fires but allow when tested as above – cropredy Oct 15 '13 at 23:08

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.