8

I can assure you that I have looked at dozens, if not hundreds of blogs, articles and questions related to SOQL limitation problems, but have not found the answer that I'm looking for. Please hear me out.

I have built a custom SalesForce Quote/Order/Change Order process using only custom objects. The only standard object that we use is Leads, and even those are soon to go.

My problem is that our sales process is massive and includes many VisualForce pages and triggers. All of the data originates in the Lead and transforms many times through the sales process. In addition, our product type is very complex and requires 10 custom objects to facilitate. I would like my test class to not rely on any existing data because this is best practice, and I don't want to have to maintain dummy data.

So, in order to test all of my code, I need to be able to do the following in a single test:

  • Generate a subset of our product data.
  • Create a Lead
  • Work through the entire massive sales process from start to finish

Here are the problems I'm facing:

  • By the time I've gotten all of the way through to the end of the process I am well over my SOQL limits.
  • Though I would like to break my test classes up, the final portion of my sales process (Orders and Change Orders) rely on data that is generated by triggers and visualforce pages which would have fired previously in the test. There is no way for me to break up the test and still access all of the generated data that I need.
  • I've played with Test.startTest() and Test.stopTest() with some success, but since I can only have one test class, I can only use these once.

What I have tried

  • I have moved the start/stopTest() methods to various positions in my test to try and leverage as many calls as possible
  • I have attempted to use multiple methods within a single class (fig 1) so that I can use the start/stopTest() methods multiple times. This doesn't work because I can't reference the data from one method to the next.
  • I have tried chaining methods (fig 2), but this doesn't work because test methods need to be static void (I believe).

I'm hoping that I have totally misunderstood how test methods are supposed to work and that someone can lead me down the correct path. What I suspect people will say is that I need to split up my class -- but I really can't do that with out creating dummy data which would need to be manually maintained. I would really like to avoid @isTest(SeeAllData=true).

fig 1

private class OrderTest {
    @isTest static void orderProcess1() { ... }
    @isTest static void orderProcess2() { ... }
    @isTest static void orderProcess3() { ... }
}

fig 2

private class orderTest {
    @isTest static void createLead() {
        Lead newLead = new Lead ( ... );
        // Begin next method
        convertLead(newLead);
    }
    @isTest static void convertLead(Lead newLead) {
        Test.startTest();
        // Convert Lead
        Test.stopTest();
    }
}
  • 3
    +1 for asserting I can assure you that I have looked at dozens, if not hundreds of blogs, articles and questions related to SOQL limitation problems, and a well-written question , nicely-styled question. If only everyone would be so good. BTW - seeAllData=true is never,never a good idea (except in very limited situations where mocking of certain standard objects is impossible) and you should mentally associate that @istest annotation with putting your finger in an electric outlet. – cropredy Dec 31 '15 at 0:26
10

I know you said you wanted to avoid breaking the test classes up, but that's exactly what I suggest. Hear me out.

One of the things you can do (and I suggest you do) is simply bypass those trigger dependencies by inserting the data as it should look later in the process. For example, you'd have a class to test the first part of the process and then a second class (or method, even) to test the latter part. In this second method, you can insert data pre-processed. Since, theoretically, you tested this pre-processesing in the first method, you really won't be cheating by manually entering in the second.

Also, though this won't help with SoQL but with readability, I recommend you leverage the @testSetup pattern to do a pre-inserting of records you will need in both methods. Then you won't have to write a big part of the inserts more than once.

Example of the testSetup pattern:

@isTest
private class TestClass {
    @TestSetup
    public static void setup() {
      //insert some records here
    }

    @isTest static void testMethod1() {
      //do stuff
      //If you need to manipulate records from the setup() method query for them here
      Test.startTest();
      //Do more stuff
      Test.stopTest();
      //asserts could go here
    }
}
  • 3
    Also, it helps if you have a mechanism that allows you to disable triggers on-demand, which could allow you to set up records without all those intervening triggers messing you up. – sfdcfox Dec 31 '15 at 0:25
7

I have ran into this same problem before and the @testSetup has solved most of it.

However, if I understand you correctly, testSetup may not allow you to set up all your data as you want.

The problem here is with your "way of thinking" I understand you would like to do end to end testing, BUT, in real practice, no user would do all of that in one transaction (VF page stuff, etc). So attempting to test in in one big test method is not actually testing real life situations.

I would structure the test as such:

  1. TestSetup method - Sets up all required base data (config, related lookup tables, objects used for reference, etc);
  2. Create test method to cover each step of the process you are testing as it relates to a single transaction. If the user cannot do it in one transaction then do not have your test do it.
  3. If you are testing controllers / utility classes etc, do test class individually for each of those testing their functionality as well as appropriate error handling.

Designed properly, you do not have to test end to end in a single method as if the sum of all parts works then the whole part is tested. Make sense?

When it comes to testing processes that are way down in the chain of events, if you have tested up until that point, it is ok to create the records using simple DML inserts of what they would look like up until that point. You do not have to use a VF controller for example to create the record, simply use DML to create the data as it would look if the user had successfully reached that point.

  • So I guess what it boils down to (and what @Sebastian Kessel is saying as well) is that I will likely need to use DML inserts to create the records as I would expect them to look, rather than relying on my previous code to generate the records. – VictorKilo Dec 30 '15 at 23:26
  • @VictorKilo - Yes. And if you test the processes that would have created those record you already know they are working as expected so no need to go through it for every other test down the line. – Eric Dec 30 '15 at 23:27
  • That seems reasonable. I think I was just getting caught up on not having to manually create too many records. I liked the idea of relying on my triggers to generate the records -- but I think I like the idea of the class passing more. ;) – VictorKilo Dec 30 '15 at 23:29
  • 1
    Thanks Victor... and Eric's answer is nearly identical to mine. I wish I could share reputation (though he has plenty more than I do :D )... I did upvote it though – Sebastian Kessel Dec 30 '15 at 23:35
  • 1
    Its ok all. Not a competition. If you got something out of the answers that is all that is needed. Thats the great thing about the community! – Eric Dec 30 '15 at 23:50
3

In addition to what Sebastian said you can take it one step further and add do something like this.. This will allow you break out each and every situation and it will give you more control over what tests run, and how..

private static void init(Integer testType)
{
 //declare lists, maps, whatever
  if(testType == 1)
  {
       //build testdata
  }
  if(testType == 2)
  {
       //build testdata
  } 
}

 private static testMethod void dostuff1()
{
   init(1);

   Test.startTest();

    //run test logic

    Test.stopTest();

    //asserts

}

 private static testMethod void dostuff2()
{
    init(2);

    Test.startTest();

    //run test logic

    Test.stopTest();

    //asserts
}
  • Thanks! I like this technique for being able to split up some of the setup methods. I would like to note though that there is a benefit to trying to fit as much of the setup into a @TestSetup as noted in the link below. I am setting up my product data in a @TestSetup method while I use your setup method for my order/customer data. Referenced Link: developer.salesforce.com/releases/release/Spring15/TestClasses – VictorKilo Dec 31 '15 at 1:22

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.