apex-enterprise-patterns / fflib-apex-mocks

An Apex mocking framework for true unit testing in Salesforce, with Stub API support
BSD 3-Clause "New" or "Revised" License
422 stars 214 forks source link

How to: ApexMocks - dependency on uow.commitWork() ID creation #52

Closed cropredy closed 6 years ago

cropredy commented 7 years ago

I'll be the first to admit I'm no expert in ApexMocks but do understand fflib_ApexCommon quite well

Class/Method under test - inserts an SObject and then uses the inserted SObject's ID to call an email service. Example is simplified from real work requirement

public class Foo {
  public void doWork() {
     fflib_ISobjectUnitOfWork uow = Application.UnitOfWork.newInstance();
     Account a = new Account(Name='A0', Website = 'www.salesforce.com');
     uow.registerNew(a); 
     uow.commitWork();
     EmailService.sendEmail(a.Id);  // relies on commitWork inserting the Account
  }
}

TestMethod

private class TestApexMocks {
   @isTest private static void testFoo() {
     fflib_ApexMocks mocks = new fflib_ApexMocks();
     // Given mock implementation of UnitOfWork
     fflib_SobjectUnitOfWork mockUow    = (fflib_SobjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class);
     Application.UnitOfWork.setMock(mockUow);

    // Given mock implementation of EmailService
    EmailServiceImpl mockEmailSvc = (EmailServiceImpl) mocks.mock(EmailServiceImpl.class);
    Application.Service.setMock(IEmailService.class,mockEmailSvc);

     // When method invoked
     new Foo().doWork();

    // Then verify EmailService called with inserted Account ID - How??
     ((EmailServiceImpl) mocks.verify(mockEmailSvc,mocks.times(1)))
                      .sendEmail(??);

}

Essentially the issue is that uow.commitWork() is a void method so thenReturn doesn't help. uow.commitWork() has a side effect, new SObjects get IDs - but if you are mocking uow, how does one mock that side effect so the code under test gets that ID in variable a?

The meta comment here is that unit testing methods that use the Unit of Work layer is difficult when the code relies on the values of inserted sobjects to do further work such as in my EmailService example, or calling an async method like future or queueable. AFAIK, you can't verify the payloads to these follow-on services if those payloads include IDs from the committed new SObjects.

I remain tantalized by ApexMocks to make my tests faster and easier to set up but figuring out how to verify when no real DML is being done by the testmethod is perplexing/challenging.

XoNoXForce commented 7 years ago

Hi, what you are describing looks a perfect fit for the fflib-Answer . V

On Fri, 15 Sep 2017 at 18:41, cropredy notifications@github.com wrote:

I'll be the first to admit I'm no expert in ApexMocks but do understand fflib_ApexCommon quite well

Class/Method under test - inserts an SObject and then uses the inserted SObject's ID to call an email service. Example is simplified from real work requirement

public class Foo { public void doWork() { fflib_ISobjectUnitOfWork uow = Application.UnitOfWork.newInstance(); Account a = new Account(Name='A0', Website = 'www.salesforce.com'); uow.registerNew(a); uow.commitWork(); EmailService.sendEmail(a.Id); // relies on commitWork inserting the Account } }

TestMethod

private class TestApexMocks { @isTest private static void testFoo() { fflib_ApexMocks mocks = new fflib_ApexMocks(); // Given mock implementation of UnitOfWork fflib_SobjectUnitOfWork mockUow = (fflib_SobjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class); Application.UnitOfWork.setMock(mockUow);

// Given mock implementation of EmailService
EmailServiceImpl mockEmailSvc = (EmailServiceImpl) mocks.mock(EmailServiceImpl.class);
Application.Service.setMock(IEmailService.class,mockEmailSvc);

 // When method invoked
 new Foo().doWork();

// Then verify EmailService called with inserted Account ID - How??
 ((EmailServiceImpl) mocks.verify(mockEmailSvc,mocks.times(1)))
                  .sendEmail(??);

}

Essentially the issue is that uow.commitWork() is a void method so thenReturn doesn't help. uow.commitWork() has a side effect, new SObjects get IDs - but if you are mocking uow, how does one mock that side effect so the code under test gets that ID in variable a?

The meta comment here is that unit testing methods that use the Unit of Work layer is difficult when the code relies on the values of inserted sobjects to do further work such as in my EmailService example, or calling an async method like future or queueable. AFAIK, you can't verify the payloads to these follow-on services if those payloads include IDs from the committed new SObjects.

I remain tantalized by ApexMocks to make my tests faster and easier to set up but figuring out how to verify when no real DML is being done by the testmethod is perplexing/challenging.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/financialforcedev/fflib-apex-mocks/issues/52, or mute the thread https://github.com/notifications/unsubscribe-auth/AS4EDy_vvCD10igiZI1Hr11i8XXKn0xIks5sira3gaJpZM4PZSzS .

cropredy commented 7 years ago

aha - this is where not having any previous background with Mockito is biting me; I found this blog post https://xonoxforce.wordpress.com/2017/03/31/answering-with-apex-mocks/ - I'm guessing this is you ? Well, this should be fun to try.

the andyinthecloud blog should be extended to illustrate more use cases with ApexMocks, Sobjects, and fflib. The examples we have today are too simple once you start using ApexMocks for real world cases

On Fri, Sep 15, 2017 at 11:23 AM, Enzo Denti notifications@github.com wrote:

Hi, what you are describing looks a perfect fit for the fflib-Answer . V

On Fri, 15 Sep 2017 at 18:41, cropredy notifications@github.com wrote:

I'll be the first to admit I'm no expert in ApexMocks but do understand fflib_ApexCommon quite well

Class/Method under test - inserts an SObject and then uses the inserted SObject's ID to call an email service. Example is simplified from real work requirement

public class Foo { public void doWork() { fflib_ISobjectUnitOfWork uow = Application.UnitOfWork.newInstance(); Account a = new Account(Name='A0', Website = 'www.salesforce.com'); uow.registerNew(a); uow.commitWork(); EmailService.sendEmail(a.Id); // relies on commitWork inserting the Account } }

TestMethod

private class TestApexMocks { @isTest private static void testFoo() { fflib_ApexMocks mocks = new fflib_ApexMocks(); // Given mock implementation of UnitOfWork fflib_SobjectUnitOfWork mockUow = (fflib_SobjectUnitOfWork) mocks.mock(fflib_SObjectUnitOfWork.class); Application.UnitOfWork.setMock(mockUow);

// Given mock implementation of EmailService EmailServiceImpl mockEmailSvc = (EmailServiceImpl) mocks.mock(EmailServiceImpl.class); Application.Service.setMock(IEmailService.class,mockEmailSvc);

// When method invoked new Foo().doWork();

// Then verify EmailService called with inserted Account ID - How?? ((EmailServiceImpl) mocks.verify(mockEmailSvc,mocks.times(1))) .sendEmail(??);

}

Essentially the issue is that uow.commitWork() is a void method so thenReturn doesn't help. uow.commitWork() has a side effect, new SObjects get IDs - but if you are mocking uow, how does one mock that side effect so the code under test gets that ID in variable a?

The meta comment here is that unit testing methods that use the Unit of Work layer is difficult when the code relies on the values of inserted sobjects to do further work such as in my EmailService example, or calling an async method like future or queueable. AFAIK, you can't verify the payloads to these follow-on services if those payloads include IDs from the committed new SObjects.

I remain tantalized by ApexMocks to make my tests faster and easier to set up but figuring out how to verify when no real DML is being done by the testmethod is perplexing/challenging.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/financialforcedev/fflib-apex-mocks/issues/52, or mute the thread https://github.com/notifications/unsubscribe-auth/AS4EDy_ vvCD10igiZI1Hr11i8XXKn0xIks5sira3gaJpZM4PZSzS .

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/financialforcedev/fflib-apex-mocks/issues/52#issuecomment-329862744, or mute the thread https://github.com/notifications/unsubscribe-auth/ACNrcqNq_CDiQPMYNpJ-mV8QSsBZ9l7Dks5sisCxgaJpZM4PZSzS .

afawcett commented 6 years ago

@cropredy will do, thanks for the nudge! 👍