2

I need to write some unit tests for a project that involve some complex account hierarchies where most, if not all are related to some kind of master account or parent account that I can't create from "scratch" in a unit test. Instead, I need to reference it in some manner when creating the child accounts for testing or things will fail to execute properly because of validation, etc.

Other than importing it as a static resource, what other options are available to import the primary account into the test class without using SeeAllData=true? I don't want to hard code the parent account Id into the test class.

9
  • 2
    why can't you mock a hierarchy with a series of DML inserts in the testmethod? I'm missing something here – cropredy Dec 16 '15 at 22:30
  • There's a specific account number that has to be used for the parent account in the hierarchy. I can't create that account in the test class. I can create all the rest, but can't duplicate the one at the top. – crmprogdev Dec 16 '15 at 23:03
  • It seems like you should use Test.loadData. – Adrian Larson Dec 16 '15 at 23:05
  • @AdrianLarson, That's what I was thinking but wanted to see if there was an alternative. That's what I was referring to when I said importing from a static resource. Apologies for the imprecise terminology. Brain gets tired near the end of the work day. – crmprogdev Dec 16 '15 at 23:06
  • ah --- then in your PROD code, create an Interface - IUltimateParent with a single method get(). Then, in PROD code, implement a concrete implementation of the method that fetches the PROD master Account. In the testmethod, create a separate implementation that returns a mocked Account. The testmethod sets the value of the PROD's interface variable to point at the test interface's implementation – cropredy Dec 16 '15 at 23:08
3

I'm not saying this will solve your problem as I can tell you are holding back underlying complexities but it is an alternative to Test.loadData where you need a lot of control over your other-unmockable-by-DML test data.

Production class

public class MyProdClass {
 public interface IUltimateAccount {
   Account get();
 }

 public class ProdUltimateAccount implements IUltimateAccount {
  public Account get() {
     return [select ... from Account ... ];  // your logic here
 }

 // default the variable representing the interface to the PROD version. Testmethod can override
 @TestVisible private IUltimateAccount iUltimateAccount = new ProdUltimateAccount();

 // constructor
 public MyProdClass() {}

 ....  somewhere where you need the ultimate (highest) account
 public void doSomethingUseful() {
   Account theUltimateAcct = iUltimateAccount.get();
   .. and exploit it
 }
}

Test Class and Method

@isTest
private with sharing MyTestClass {

public class IMockUltimateAccount implements MyProdClass.IUltimateAccount {
  String useCase;

  public IMockUltimateAccount(String useCase) {this.useCase = useCase;}

  public Account get() {
    Map<String,Account> useCaseToAccountMap = new Map<String,Account> {
                           'usecase1' =>  new Account(fldA = 'foo', ...),
                           'usecase2' =>  new Account(fldA = 'bar', ...),
                           ...
                        };
    return useCaseToAccountMap.get(this.useCase);
  }
 }

...
@isTest
private static void testMyStuff() {
   MyProdClass cls = new MyProdClass();
   cls.iUltimateAccount = new IMockUltimateAccount('usecase1');

   // prod class uses testmethod's interface to return usecase-specific mocked ultimate Account     
   cls.doSomethingUseful(); 
   system.assert(somethingUseful); 
}
}

Notes

  1. The testmethod, if it needs to mock specific auto-numbers or ID fields, use Json.deserialize instead of the Account constructors as shown
  2. The only thing this technique does is avoid the need to actually insert a mocked record into the database, instead, you return the sObject as if it had been retrieved via SOQL
  3. If you need to build a hierarchy of mocked Accounts, this may get complex
  4. You have to anticipate everywhere in the PROD class where the ultimate Account is fetched and ensure all those use cases go through the interface's get() method.
11
  • Thank you for posting this. Extremely helpful. At this point, I believe I'm only going to need to pass the Id of the account at the top of the hierarchy (over 500 fields in account object). And yes, it does need to be a specific account. There's no autonumber involved. I just need the specific account. It sounds like I'll need to roundtrip serialize & deserialize based on what you've posted. – crmprogdev Dec 18 '15 at 15:57
  • Prob with this line: public class IMockUltimateAccount implements MyProdClass.IUltimateAccount. Won't compile with a class name in dot notation for implementing Mock as an interface of production class (invalid type). Was that a typo on your part or have I missed something? Am having probs calling it from test class as result (more invalid type errors). – crmprogdev Dec 21 '15 at 17:56
  • i edited the answer - the testmethod's concrete implementation of the interface is an inner class to the test class and hence readily usable by the testmethod – cropredy Dec 21 '15 at 18:09
  • Thnx! That worked and is much clearer. Was @TestVisible private iUltimateAccount = new ProdUltimateAccount(); to go in the Production class? I couldn't get that to work. I believe it's a form of type casting? If not, why I am I calling the instance? Otherwise, I must be missing something in the implementation. Trying to learn to be a better fisherman. ;) – crmprogdev Dec 21 '15 at 20:32
  • yes, since the testmethod has to set the value of the Prod class variable iUltimateAccount , that variable needs to be either public or private+@TestVisible to the testmethod. What the testmethod is doing is swapping out the default (PROD) value of the interface object to the one used by the testmethod so when the prod class refers to the interface variable (while running in the test context), the testmethod's implementation of the interface is used – cropredy Dec 21 '15 at 21: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.