Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fflib_MethodVerifier could use more diagnostics #78

Open
cropredyHelix opened this issue Aug 28, 2019 · 0 comments
Open

fflib_MethodVerifier could use more diagnostics #78

cropredyHelix opened this issue Aug 28, 2019 · 0 comments

Comments

@cropredyHelix
Copy link
Contributor

the method throwException currently displays

Expected n, Actual m -- Wanted by not invoked <the qualified method> <custom message>

which can lead to head scratching when one tries to figure out why the verify fails.

  • Is the matcher wrong in the testmethod?
  • Is the code under test wrong?
  • Are the calls in the wrong order (fflib_InOrder use case)?

Since ApexMocks is recording all the actual method calls and the verify methods are comparing a single method call against all recorded calls of that method; it would seem useful to the developer to display an enumeration of the recorded method calls when this exception is thrown.

Here's an example where this would have helped me (example is a simplified version of the real code under test)

Code Under Test

public with sharing class MyCode {

	public void doStuff(List<Id> list0, List<Id> list1) {

		StuffService.add(list0);
		list0.clear();  // pay attention to this line
		StuffService.add(list1);
	}
} 

Service class being mocked

public with sharing class StuffServiceImpl implements IStuffService {
	static List<id> ids = new List<Id>();

	public void add(List<Id> idsToAdd) {
		ids.addAll(idsToAdd);
	}
}

Test method

@IsTest
private class MyCodeTest {
  @IsTest
  static void testInorder() {

    //	Given test data
    Id[] mockIds = new List<Id> {
			fflib_IDGenerator.generate(Account.SObjectType),
			fflib_IDGenerator.generate(Account.SObjectType),
			fflib_IDGenerator.generate(Account.SObjectType),
			fflib_IDGenerator.generate(Account.SObjectType)
    };
    //	Given mocks framework
    fflib_ApexMocks mocks = new fflib_ApexMocks();

    // Given mock Service
    StuffServiceImpl mockStuffService = (StuffServiceImpl) mocks.mock(StuffServiceImpl.class);
    // Given mock Service injected
    Application.Service.setMock(IStuffService.class,mockStuffService);

    //	Given inOrderMocks
    fflib_InOrder mocksInOrder = 
       new fflib_InOrder(mocks,new List<Object> {mockStuffService});

    //	Given code to test
    MyCode myCode = new MyCode();

    //	When called with distinct sets of Ids
    myCode.doStuff(new List<id> {mockIds[0],mockIds[1]},
				new List<Id> {mockIds[2],mockIds[3]});

    //	Then verify order of calls to mock service
   ((StuffServiceImpl)mocksInOrder.verify(mockStuffService,mocks.calls(1)
				.description('service sb called for ids[0] and [1]')))
				.add(new List<Id> {mockIds[0],mockIds[1]});
    ((StuffServiceImpl)mocksInOrder.verify(mockStuffService,mocks.calls(1)
				.description('service sb called for ids[2] and [3]')))
				.add(new List<Id> {mockIds[2],mockIds[3]});

  }
}

The first verify fails with

fflib_ApexMocks.ApexMocksException: In Order: Expected : 1, Actual: 0 -- Wanted but not invoked: StuffServiceImpl__sfdc_ApexStub.add(List<Id>). service sb called for ids[0] and [1].

...head scratching...

The reason why is that ApexMocks captures method non-primitive arguments by reference (rather than cloning them -- which is admittedly not always possible), so if the code under test changes the captured non-primitive argument before the ApexMocks verify is executed, the argument being compared (in my case):


((StuffServiceImpl)mocksInOrder.verify(mockStuffService,mocks.calls(1)
				.description('service sb called for ids[0] and [1]')))
				.add(new List<Id> {mockIds[0],mockIds[1]});

is no longer is equal to the argument that was captured because the code under test had cleared that argument! with list0.clear();

Five hours of my life went into discovering this after debugging the guts of ApexMocks and seeing that fflib_InOrder.verifyMethodCall was returning via getNextMethodCall() an argument of an empty list even though during method recording, the recorded arg was a list of two Ids. I should not have to dig into ApexMocks internals to see why the matching failed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants