0

I was writing tests for my invoice parser class, which contains 5 methods to test, ans 3 of those tests fail, I can't figure out what the problem is. Here is my test class:

@IsTest
private class InvoiceParserTest {
    @IsTest
    static void testCreateProduct() {
        Product2 product2 = new Product2(Name='Test Product');
        insert product2;
        List<Product2> product2s = [SELECT Id FROM Product2 WHERE Name=:product2.Name];
        System.assert(product2s.size() > 0);
    }

    @IsTest
    static void testCreatePriceBookEntry() {
        Product2 product2 = new Product2(Name='New Test Product');
        insert product2;
        Pricebook2 pricebook2 = new Pricebook2(Name='New Test Price Book', IsActive=true);
        insert pricebook2;
        Pricebook2 standardPricebook2 = [SELECT Id FROM Pricebook2 WHERE IsStandard=TRUE];
        PricebookEntry standardPriceBookEntry = new PricebookEntry(
                Pricebook2Id = standardPricebook2.Id, Product2Id = product2.Id,
                UnitPrice = 100, IsActive=true
        );
        PricebookEntry pricebookEntry = new PricebookEntry(
                UnitPrice = 10,
                Pricebook2Id = pricebook2.Id,
                Product2Id = product2.Id,
                IsActive=true
        );
        insert standardPriceBookEntry;
        insert pricebookEntry;
        List<PricebookEntry> pricebookEntries = [SELECT Id FROM PricebookEntry
                                                 WHERE Pricebook2Id=:pricebook2.Id];
        System.assertEquals(1, pricebookEntries.size());
    }

    @IsTest
    static void testCreatePricebook() {
        Pricebook2 pricebook2 = new Pricebook2(Name='Test Price Book', IsActive=true);
        insert pricebook2;
        List<Pricebook2> pricebook2s = [SELECT Name FROM Pricebook2 WHERE IsActive=TRUE];
        System.assertEquals(1, pricebook2s.size());
    }

    @IsTest
    static void testCreateOpportunity() {
        Opportunity opportunity = new Opportunity(Name='Test Opportunity', StageName='Closed Won',
                CloseDate=Date.today());
        insert opportunity;
        List<Opportunity> opportunities = [SELECT Id FROM Opportunity WHERE StageName LIKE 'Closed'];
        System.assert(opportunities.size() > 0);
    }

    @IsTest
    static void testSendInvoiceDate() {
        Product2 product2 = new Product2(Name='New Test Product');
        insert product2;
        Pricebook2 pricebook2 = new Pricebook2(Name='New Test Price Book', IsActive=true);
        insert pricebook2;
        Pricebook2 standardPricebook2 = [SELECT Id FROM Pricebook2 WHERE IsStandard=TRUE];
        PricebookEntry standardPriceBookEntry = new PricebookEntry(
                Pricebook2Id = standardPricebook2.Id, Product2Id = product2.Id,
                UnitPrice = 100, IsActive=true
        );
        PricebookEntry pricebookEntry = new PricebookEntry(
                UnitPrice = 10,
                Pricebook2Id = pricebook2.Id,
                Product2Id = product2.Id,
                IsActive=true
        );
        insert standardPriceBookEntry;
        insert pricebookEntry;
        Opportunity opportunity = new Opportunity(Name='Test Opportunity', StageName='Closed Won',
                CloseDate=Date.today());
        insert opportunity;
        List<OpportunityLineItem> opportunityLineItems = new List<OpportunityLineItem>{
                new OpportunityLineItem(
                        Bill_From__c = 'TestCompany', Biil_To__c = 'John Lee',
                        Invoice_Number__c = 1,
                        Invoice_Date__c = Date.today(),
                        Invoice_Total__c = 20, Invoice_Tax__c = 5,
                        InvoiceAmountDue__c = 33,
                        Invoice_Subtotal__c = 45l,
                        Item_Name__c =  'Test Item 1',
                        Description__c = 'Test description',
                        Quantity = 2,
                        Item_Cost__c = 1.0,
                        TotalPrice = 2,
                        OpportunityId = opportunity.Id,
                        PricebookEntryId=pricebookEntry.Id
                ),
                new OpportunityLineItem(
                        Bill_From__c = 'TestCompany2', Biil_To__c = 'John Lee',
                        Invoice_Number__c = 1,
                        Invoice_Date__c = Date.today(),
                        Invoice_Total__c = 20, Invoice_Tax__c = 5,
                        InvoiceAmountDue__c = 33,
                        Invoice_Subtotal__c = 45l,
                        Item_Name__c =  'Test Item 2',
                        Description__c = 'Test description 2',
                        Quantity = 4,
                        Item_Cost__c = 1.0,
                        TotalPrice = 4,
                        OpportunityId = opportunity.Id,
                        PricebookEntryId=pricebookEntry.Id
                )
        };
        insert opportunityLineItems;
        List<OpportunityLineItem> olisQuery = [SELECT Id FROM OpportunityLineItem
                                               WHERE Id = :opportunity.Id];
        System.assert(olisQuery.size() > 0);
    }
}

And here's what the log says:

Testing started at 4:12 PM ...

Using connection 'invoiceParserApp'.

Apex log for InvoiceParserTest.testCreateOpportunity
====================================================
55.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;NBA,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WAVE,INFO;WORKFLOW,INFO
14:12:50.13 (13874326)|EXECUTION_STARTED
14:12:50.13 (13967395)|CODE_UNIT_STARTED|[EXTERNAL]|01p7R000009BgJx|InvoiceParserTest.testCreateOpportunity()
14:12:50.13 (26790736)|DML_BEGIN|[51]|Op:Insert|Type:Opportunity|Rows:1
14:12:50.13 (249862962)|DML_END|[51]
14:12:50.13 (250417758)|SOQL_EXECUTE_BEGIN|[52]|Aggregations:0|SELECT Id FROM Opportunity WHERE StageName LIKE 'Closed'
14:12:50.13 (276288389)|SOQL_EXECUTE_END|[52]|Rows:0
14:12:50.13 (277188969)|EXCEPTION_THROWN|[53]|System.AssertException: Assertion Failed
14:12:50.13 (278308872)|FATAL_ERROR|System.AssertException: Assertion Failed

Class.InvoiceParserTest.testCreateOpportunity: line 53, column 1
14:12:50.13 (278344196)|FATAL_ERROR|System.AssertException: Assertion Failed

Class.InvoiceParserTest.testCreateOpportunity: line 53, column 1
14:12:50.278 (278370047)|CUMULATIVE_LIMIT_USAGE
14:12:50.278 (278370047)|LIMIT_USAGE_FOR_NS|(default)
  Number of SOQL queries: 1 out of 100
  Number of query rows: 0 out of 50000
  Number of SOSL queries: 0 out of 20
  Number of DML statements: 1 out of 150
  Number of Publish Immediate DML: 0 out of 150
  Number of DML rows: 1 out of 10000
  Maximum CPU time: 0 out of 10000
  Maximum heap size: 0 out of 6000000
  Number of callouts: 0 out of 100
  Number of Email Invocations: 0 out of 10
  Number of future calls: 0 out of 50
  Number of queueable jobs added to the queue: 0 out of 50
  Number of Mobile Apex push calls: 0 out of 10

14:12:50.278 (278370047)|CUMULATIVE_LIMIT_USAGE_END
14:12:50.13 (278554111)|CODE_UNIT_FINISHED|InvoiceParserTest.testCreateOpportunity()
14:12:50.13 (278906332)|EXECUTION_FINISHED

System.AssertException: Assertion Failed
Class.InvoiceParserTest.testCreateOpportunity: line 53, column 1

Apex log for InvoiceParserTest.testCreatePriceBookEntry
=======================================================
55.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;NBA,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WAVE,INFO;WORKFLOW,INFO
14:12:50.306 (306137319)|EXECUTION_STARTED
14:12:50.306 (306142735)|CODE_UNIT_STARTED|[EXTERNAL]|01p7R000009BgJx|InvoiceParserTest.testCreatePriceBookEntry()
14:12:50.306 (307605268)|DML_BEGIN|[18]|Op:Insert|Type:Product2|Rows:1
14:12:50.306 (332641913)|DML_END|[18]
14:12:50.306 (333648119)|DML_BEGIN|[20]|Op:Insert|Type:Pricebook2|Rows:1
14:12:50.306 (364197933)|DML_END|[20]
14:12:50.306 (364611832)|SOQL_EXECUTE_BEGIN|[21]|Aggregations:0|SELECT Id FROM Pricebook2 WHERE IsStandard = TRUE
14:12:50.306 (369292629)|SOQL_EXECUTE_END|[21]|Rows:0
14:12:50.306 (369754839)|FATAL_ERROR|System.QueryException: List has no rows for assignment to SObject

Class.InvoiceParserTest.testCreatePriceBookEntry: line 21, column 1
14:12:50.306 (369773216)|FATAL_ERROR|System.QueryException: List has no rows for assignment to SObject

Class.InvoiceParserTest.testCreatePriceBookEntry: line 21, column 1
14:12:50.369 (369782466)|CUMULATIVE_LIMIT_USAGE
14:12:50.369 (369782466)|LIMIT_USAGE_FOR_NS|(default)
  Number of SOQL queries: 1 out of 100
  Number of query rows: 0 out of 50000
  Number of SOSL queries: 0 out of 20
  Number of DML statements: 2 out of 150
  Number of Publish Immediate DML: 0 out of 150
  Number of DML rows: 2 out of 10000
  Maximum CPU time: 0 out of 10000
  Maximum heap size: 0 out of 6000000
  Number of callouts: 0 out of 100
  Number of Email Invocations: 0 out of 10
  Number of future calls: 0 out of 50
  Number of queueable jobs added to the queue: 0 out of 50
  Number of Mobile Apex push calls: 0 out of 10

14:12:50.369 (369782466)|CUMULATIVE_LIMIT_USAGE_END
14:12:50.306 (369831705)|CODE_UNIT_FINISHED|InvoiceParserTest.testCreatePriceBookEntry()
14:12:50.306 (369865844)|EXECUTION_FINISHED

System.QueryException: List has no rows for assignment to SObject
Class.InvoiceParserTest.testCreatePriceBookEntry: line 21, column 1

Apex log for InvoiceParserTest.testSendInvoiceDate
==================================================
55.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;NBA,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WAVE,INFO;WORKFLOW,INFO
14:12:50.383 (383419648)|EXECUTION_STARTED
14:12:50.383 (383428012)|CODE_UNIT_STARTED|[EXTERNAL]|01p7R000009BgJx|InvoiceParserTest.testSendInvoiceDate()
14:12:50.383 (384091341)|DML_BEGIN|[59]|Op:Insert|Type:Product2|Rows:1
14:12:50.383 (396913867)|DML_END|[59]
14:12:50.383 (397116600)|DML_BEGIN|[61]|Op:Insert|Type:Pricebook2|Rows:1
14:12:50.383 (435362887)|DML_END|[61]
14:12:50.383 (435953377)|SOQL_EXECUTE_BEGIN|[62]|Aggregations:0|SELECT Id FROM Pricebook2 WHERE IsStandard = TRUE
14:12:50.383 (443617537)|SOQL_EXECUTE_END|[62]|Rows:0
14:12:50.383 (443916969)|FATAL_ERROR|System.QueryException: List has no rows for assignment to SObject

Class.InvoiceParserTest.testSendInvoiceDate: line 62, column 1
14:12:50.383 (443935061)|FATAL_ERROR|System.QueryException: List has no rows for assignment to SObject

Class.InvoiceParserTest.testSendInvoiceDate: line 62, column 1
14:12:50.443 (443943373)|CUMULATIVE_LIMIT_USAGE
14:12:50.443 (443943373)|LIMIT_USAGE_FOR_NS|(default)
  Number of SOQL queries: 1 out of 100
  Number of query rows: 0 out of 50000
  Number of SOSL queries: 0 out of 20
  Number of DML statements: 2 out of 150
  Number of Publish Immediate DML: 0 out of 150
  Number of DML rows: 2 out of 10000
  Maximum CPU time: 0 out of 10000
  Maximum heap size: 0 out of 6000000
  Number of callouts: 0 out of 100
  Number of Email Invocations: 0 out of 10
  Number of future calls: 0 out of 50
  Number of queueable jobs added to the queue: 0 out of 50
  Number of Mobile Apex push calls: 0 out of 10

14:12:50.443 (443943373)|CUMULATIVE_LIMIT_USAGE_END
14:12:50.383 (443981869)|CODE_UNIT_FINISHED|InvoiceParserTest.testSendInvoiceDate()
14:12:50.383 (443990632)|EXECUTION_FINISHED

System.QueryException: List has no rows for assignment to SObject
Class.InvoiceParserTest.testSendInvoiceDate: line 62, column 1

And this is the class to test

/**
 * Created by odrozd on 8/26/22.
 */

public with sharing class InvoiceParser {
    private static HttpResponse response = InvoiceCallout.makeGetCallout();
    private static Invoice invoice = (Invoice)JSON.deserializeStrict(response.getBody(), Invoice.class);

    private static Pricebook2 createPriceBook() {
        Pricebook2 pricebook2 = new Pricebook2(Name='New Price Book', IsActive=true);
        insert pricebook2;
        return pricebook2;
    }

    private static Opportunity createOpportunity() {
        Opportunity opportunity = new Opportunity(Name='New Opportunity', StageName='Closed Won',
                CloseDate=Date.valueOf(invoice.invoiceDate));
        insert opportunity;
        return opportunity;
    }

    private static PricebookEntry createPriceBookEntry(Integer invoiceItem) {
        Product2 product2 = createProduct(invoiceItem);
        Pricebook2 pricebook2 = createPriceBook();
        Pricebook2 standardPricebook2 = [SELECT Id FROM Pricebook2 WHERE IsStandard=TRUE];
        PricebookEntry standardPriceBookEntry = new PricebookEntry(
                Pricebook2Id = standardPricebook2.Id, Product2Id = product2.Id,
                UnitPrice = 100, IsActive=true
        );
        PricebookEntry pricebookEntry = new PricebookEntry(
                UnitPrice = Decimal.valueOf(invoice.items[invoiceItem].get('lineTotal')),
                Pricebook2Id = pricebook2.Id,
                Product2Id = product2.Id,
                IsActive=true
        );
        insert standardPriceBookEntry;
        insert pricebookEntry;
        return pricebookEntry;
    }

    private static Product2 createProduct(Integer invoiceItem) {
        Product2 product2 = new Product2(Name=invoice.items[invoiceItem].get('item'));
        insert product2;
        return product2;
    }

    @AuraEnabled
    public static void sendInvoiceData() {
        List<OpportunityLineItem> oppLineItems = new List<OpportunityLineItem>();
        PricebookEntry pricebookEntry = createPriceBookEntry(1);
        Opportunity opportunity = createOpportunity();
        for (Integer i = 0; i < invoice.items.size(); i++) {
            oppLineItems.add(new OpportunityLineItem(
                    Bill_From__c = invoice.billFrom, Biil_To__c = invoice.billTo,
                    Invoice_Number__c = invoice.invoiceNumber,
                    Invoice_Date__c = Date.valueOf(invoice.invoiceDate),
                    Invoice_Total__c = invoice.total, Invoice_Tax__c = invoice.tax,
                    InvoiceAmountDue__c = invoice.amountDue,
                    Invoice_Subtotal__c = invoice.subtotal,
                    Item_Name__c =  invoice.items[i].get('item'),
                    Description__c = invoice.items[i].get('description'),
                    Quantity = Decimal.valueOf(invoice.items[i].get('quantity')),
                    Item_Cost__c = Decimal.valueOf(invoice.items[i].get('lineCost')),
                    TotalPrice = Decimal.valueOf(invoice.items[i].get('lineTotal')),
                    OpportunityId = opportunity.Id,
                    PricebookEntryId=pricebookEntry.Id
            ));
        }
        insert oppLineItems;
    }
}

What am I doing wrong? Help me please... Thank you in advance.

3 Answers 3

1

You have three different issues here.

  1. Behavior of Pricebooks in tests
  2. Incomplete LIKE filter in a query in one of your tests
  3. Your tests aren't testing your code

Issue 1: Pricebooks in tests

Pricebooks in tests have some frustrating properties. Probably the most accurate way I can describe it is that it's a half-setup object.

  • The Pricebook2 records in your org do exist in unit tests
  • But you can't query them unless you know the Id of the pricebook (and perform a DML update on it first, e.g. update new Pricebook2(Id = myPricebookId);)
  • And you can't create a pricebook in your test that has the same name as an existing pricebook in your org (Salesforce will complain that the name already exists)

For the standard pricebook, Salesforce gives us Test.getStandardPricebookId(). Since you can get that Id directly, you don't need to query for the standard pricebook.

Trying to query it, as you are, results in no rows being returned. SOQL queries always return a List<SObject>. Salesforce allows us to assign the result of a query to a single SObject instance as a syntactic sugar/quality-of-life thing, but it requires that the query return exactly one row. If your query returns no rows, you get the error you're seeing, System.QueryException: List has no rows for assignment to SObject. If it returns >= 2 rows, you'll get a similar error System.QueryException: List has more than one row for assignment to SObject.

If you need to use an existing pricebook in your test that isn't the standard pricebook

This is a bit more of an advanced topic, but it could help other people.

The object reference for Pricebook2 lists the Name field as having the idLookup property

So we can get around the fact that we don't have the pricebook2 Ids by using the Name as a "name-pointing reference".

Basically, you can either update based on the name, then query to get the Id

update new Pricebook2(Name = 'MyPricebook');
Pricebook2 myPB = [SELECT Id FROM Pricebook2 WHERE Name = 'MyPricebook'];

or just set the SObject reference to avoid the dml + query

PricebookEntry pbe1 = new PricebookEntry(
    Product2Id = myProductId,
    // Note: We use "Pricebook2" here to set the SObject reference instead of "Pricebook2Id"
    Pricebook2 = new Pricebook2(Name = 'MyPricebook'),
    UnitPrice = 4.20
);

// Because we set the Pricebook2 reference (and only included the "Name" field, which has
//   the idLookup property)
//   Salesforce will automatically find and relate the PBE to the indicated Pricebook
insert pbe1;

Issue 2: Using LIKE in a query

This one is pretty simple. If you want to match a partial string using LIKE, you need to use wildcards. Either % to match any number (0 or more) of unknown characters, or _ to match a single unknown character.

So in testCreateOpportunity(), instead of

List<Opportunity> opportunities = [SELECT Id FROM Opportunity WHERE StageName LIKE 'Closed'];

you want

List<Opportunity> opportunities = [SELECT Id FROM Opportunity WHERE StageName LIKE 'Closed%'];

The first example (without the wildcard) is looking for a StageName that is literally and exactly "Closed". The second example looks for StageNames that start with "Closed" (and would match "Closed - Won" and "Closed - Lost")

Issue 3: Your tests should test your code

Test methods need to call the class (and the methods) that you're trying to test. You shouldn't be re-creating logic in your tests.

So for example, your testCreateProduct() test method should not be inserting a product itself. Rather, it should be calling InvoiceParser.createProduct().

Tests are responsible for 3 main items:

  1. Setting up test data (creating all of the data that the code you're trying to test requires in order to run)
  2. Calling the method in your SUT (System Under Test, i.e. the class that your test class is supposed to stress)
  3. Gathering the results, and making assertions

In the case of calling InvoiceParser.createProduct(), all you need to do is ensure that InvoiceCallout.makeGetCallout() returns some JSON data that you can use. You should look at Testing HTTP Callouts to get some guidance on creating/using a callout mock (so that your calls to InvoiceParser methods don't fail due to tests not being able to execute actual callouts).

For some tests, you may need to create more test data. Testing sendInvoiceData(), for example, would require you to:

  • Create a new Pricebook2 (or use an existing one, you almost certainly don't want to create a new Pricebook _every time you call InvoiceParser)
  • Create a new Product
  • Create a PricebookEntry or two (one to go into the standard pricebook, then one per additional pricebook that you're using in the test)... or use an existing PricebookEntry
  • Create the Opportunity (and set the Pricebook2Id appropriately)

Stuff like that is what a @TestSetup annotated method is good for. You do your common setup in a single place (and then query for the data in individual test methods, since Salesforce clears out all static variables between the @TestSetup and test methods, and between test methods) to make the individual test methods shorter and simpler.

1

In this method testCreateOpportunity you are looking for Opportunity with StageName LIKE 'Closed' (exact matching), while you have created Opportunity with StageName='Closed Won'. You have to change WHERE clause to WHERE StageName LIKE 'Closed%'. Reference to useful article.

The % wildcard matches zero or more characters.

@IsTest
static void testCreateOpportunity() {
    Opportunity opportunity = new Opportunity(Name='Test Opportunity', StageName='Closed Won',
            CloseDate=Date.today());
    insert opportunity;
    List<Opportunity> opportunities = [SELECT Id FROM Opportunity WHERE StageName LIKE 'Closed'];
    System.assert(opportunities.size() > 0);
}

In these methods testCreatePriceBookEntry, testSendInvoiceDate to access standard pricebook you should use Id pricebookId = Test.getStandardPricebookId(); instead of SOQL query:

private static PricebookEntry createPriceBookEntry(Integer invoiceItem) {
    Product2 product2 = createProduct(invoiceItem);
    Pricebook2 pricebook2 = createPriceBook();
    Id standardPricebook2Id = null;
    if (Test.isRunningTest()) {
        standardPricebook2Id = Test.getStandardPricebookId();
    } else {
        standardPricebook2Id = [SELECT Id FROM Pricebook2 WHERE IsStandard=TRUE];
    }
    
    PricebookEntry standardPriceBookEntry = new PricebookEntry(
            Pricebook2Id = standardPricebook2Id, Product2Id = product2.Id,
            UnitPrice = 100, IsActive=true
    );
    PricebookEntry pricebookEntry = new PricebookEntry(
            UnitPrice = Decimal.valueOf(invoice.items[invoiceItem].get('lineTotal')),
            Pricebook2Id = pricebook2.Id,
            Product2Id = product2.Id,
            IsActive=true
    );
    insert standardPriceBookEntry;
    insert pricebookEntry;
    return pricebookEntry;
}
6
  • Hi, Maria! In this example you calling createProduct and createPriceBook methods in the test method createPriceBookEntry but it doesn't works. As I remember we can't call methods of non-void type in test methods? Or did you mistyped something?
    – user120280
    Commented Sep 19, 2022 at 12:50
  • No, the second code sample is for InvoiceParser class, not test class. Commented Sep 19, 2022 at 13:20
  • Oh, ok. Do I need to change something in testCreatePriceBookEntity in my test class?
    – user120280
    Commented Sep 19, 2022 at 13:26
  • I don't think that it is necessary, try to implement this one and rerun tests to see if you still have any errors. Commented Sep 19, 2022 at 13:46
  • Oh also, standardPricebook2Id = [SELECT Id FROM Pricebook2 WHERE IsStandard=TRUE]; gives me an incompetable types error, and when I try to cast the right side to Id, it throws error as well, like List<Pricebook2> is never an instance of Id.
    – user120280
    Commented Sep 20, 2022 at 9:29
0

if you use the keyword LIKE in a SOQL query, you also need to use a wildcard.

I.e.

List<Opportunity> opportunities = [SELECT Id FROM Opportunity WHERE StageName LIKE 'Closed'];

should probably be including the % character as a wildcard to identify that anything after 'Closed' is allowed:

    List<Opportunity> opportunities = [SELECT Id FROM Opportunity WHERE StageName LIKE 'Closed%'];

or why not just like this (for better query performance):

    List<Opportunity> opportunities = [SELECT Id FROM Opportunity WHERE StageName = 'Closed Won'];
1
  • 1
    Have added another answer because this one doesn't cover all failed tests. Commented Sep 16, 2022 at 13:58

You must log in to answer this question.