0

I'm trying to build a database fake using StubAPI to use in Apex tests. I want to do it to save time on Database interaction (no DMLs and SOQLs) and make tests faster.

But I faced an issue with relationships. I don't see a way to put records to a related list in Apex, for example Account.Opportunities.

I tried the following things:

  1. This throws Field not found:
acc.putSObject('Opportunities', new Opportunity(
    Id = Test_AccountListCtrl.getFakeId(Opportunity.getSObjectType()),
    AccountId = acc.Id,
    Account = acc
));
  1. This simply does nothing, acc.Opportunities is still empty after that
acc.Opportunities.add(new Opportunity(
   Id = Test_AccountListCtrl.getFakeId(Opportunity.getSObjectType()),
   AccountId = acc.Id,
   Account = acc
));
  1. This throws Field is not writable:
Account acc = new Account(
    Id = getFakeId(Account.getSObjectType()),
    Name = 'Fake Account 1',
    Opportunities = new List<Opportunity> { oppty }
);
  1. This somehow throws a NullPointerException. Seems like a defect to me, cause leads to inconsistent behaviour comparing to try #2
acc.getSobjects('Opportunities').add(new Opportunity(
    Id = Test_AccountListCtrl.getFakeId(Opportunity.getSObjectType()),
    AccountId = acc.Id,
    Account = acc
));

If someone knows a way to this - please share.

6
  • this is for apex unit tests?
    – Tyler Zika
    Apr 23, 2020 at 7:48
  • @TylerZika that's right. Updated description with that note
    – nchursin
    Apr 23, 2020 at 7:49
  • why can't you insert your account, then insert the opportunities, then query for them?
    – Tyler Zika
    Apr 23, 2020 at 7:50
  • That's exactly what I'm trying to avoid :) I want to save time on the database interaction to make tests run faster
    – nchursin
    Apr 23, 2020 at 7:54
  • 1
    see this github lib - github.com/mattaddy/SObjectFabricator; also apexMocks (also github) wraps StubAPI and is more expressively powerful
    – cropredy
    Apr 23, 2020 at 17:39

2 Answers 2

3

To mock child relationships in memory, you need to use JSON.deserialize using as input the same JSON that is emitted in a REST API query result for the parent + child(ren).

This can get a bit clunky in Apex code with complex string constants, so what I use is a very expressive GitHub library called SObject Fabricator by Matthew Addy.

Here's an example taken from the readme (Account w/ 2 Opportunities):

Account acct = (Account)new sfab_FabricatedSObject(Account.class)
.setField(Account.Id, 'Id-1')
.setField(Account.LastModifiedDate, Date.newInstance(2017, 1, 1))
.setChildren('Opportunities', new List<sfab_FabricatedSObject> {
    new sfab_FabricatedSObject(Opportunity.class).setField(Opportunity.Id, 'OppId-1'), 
    new sfab_FabricatedSObject(Opportunity.class).setField(Opportunity.Id, 'OppId-2')
}).toSObject();

You can build lists of mocked objects, each with parents (and grandparents, ...) and any number of direct children.

Word of caution: Relationship names are case sensitive so in the example above, the relationship Opportunities must be spelled like that, not as opportunities.

0

I think it would be bad practice to mock the data in that way (not having it in the db). You want your test data to be just like how the data will be in prod with the code you are testing, and that means the data will about to be or be in the database at some point. To save on DMLs/SOQLs, try Creating Parent and Child Records in a Single Statement Using Foreign Keys.

You can also utilize @testSetup to speed up tests if repeat test data is used.

2
  • This isn't true. It may be appropriate to ensure that you have some tests that exercise all the way from the database to the UI, not all tests should. A Unit Test for code that does not access the database should not require the db to be there - otherwise it's not testing the UNIT of code, it's testing other code at the same time. See atlassian.com/continuous-delivery/software-testing/… for a brief overview. Note: "Unit tests are generally quite cheap to automate and can run very quickly by a continuous integration server." Jan 23 at 14:42
  • Whilst you can make DB based tests quicker by using @testSetup and grouping DMLs, it will never be as quick as building SObjects in memory and using those - often by a factor of 100. As an example of the speed you can get by avoiding DML - we have an app that has nearly 5000 apex unit tests, reaching 97% code coverage, and the tests run in around 2 minutes. Our 600 jest tests for the UI components run in 14 seconds. Jan 23 at 14:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .