0

TL;DR - Why is my api query not returning child contacts when run via a test?

Problem

Despite being included in the query, the query results from my API method does not include the Contacts column and data when run from the Execute Anonymous function nor my test class, causing my trailhead challenge to fail.

The error message I receive when attempting to complete the challenge is as follows:

Challenge not yet complete in API Study Executing the 'AccountManager' method failed. Either the service isn't configured with the correct urlMapping, is not global, does not have the proper method name or does not return the requested account and all of its contacts.

Description

I am learning about APIs and am currently on the Apex Web Services portion of the Apex Integration Services trailhead. For the hands-on challenge, I've created an apex class and test (listed below). The oddity that I'm encountering is that running the API call within the Developer Console Execute Anonymous function or via the test class does not include the "Contacts" column and data. Stranger still is that this Contacts column is included when I call the API via the Workbench.

Troubleshooting Thus Far

I've tried running the query in my getAccount class manually via the Query Editor in the Developer Console, and it always includes the queried Contacts column. I've also double-checked my getAccount code against a how-to video (link here) and it appears to be identical. I've also played with the URI, using both my sandbox-specific instance and the generic "https://yourInstance.salesforce.com/services/apexrest" code used in the trailhead and in the tutorial video I found.

Code and attempts

Account Manager Apex Class

@RestResource(urlMapping='/Account/*/contacts')
global with sharing class AccountManager {
    @HttpGet
    global static Account getAccount(){
        RestRequest request = RestContext.request;
        String accountId = request.requestURI.substringBetween('Account/', '/contacts');        
        Account result = [SELECT Id, Name, (SELECT Id, Name FROM Contacts) FROM Account WHERE Id=:accountId];
        system.debug('result: '+result);        
        return result;
    }
}

Apex Test Class

@isTest
public class AccountManagerTest {

    @isTest(SeeAllData=true)
    public static void testGetAccount(){
        
        Account a = [SELECT Id FROM Account LIMIT 1];
        // Set up a test request
        RestRequest request = new RestRequest();
        // Set request properties
        request.requestUri = 'https://cunning-fox-unspoh-dev-ed.my.salesforce.com/services/apexrest/Account/'+a.Id+'/contacts';
        request.httpMethod = 'GET';
        // Finally, assign the request to RestContext if used
        RestContext.request = request;

        Account result = AccountManager.getAccount();
        system.assertEquals(a.Id, result.Id);
    }
    
}

Workbench Rest Explorer call

enter image description here

Results from Workbench

{
    "attributes": {
        "type": "Account",
        "url": "/services/data/v51.0/sobjects/Account/0015e000009T7JhAAK"
    },
    "Id": "0015e000009T7JhAAK",
    "Name": "Dickenson plc",
    "Contacts": {
        "totalSize": 1,
        "done": true,
        "records": [{
            "attributes": {
                "type": "Contact",
                "url": "/services/data/v51.0/sobjects/Contact/0035e000006WP0LAAW"
            },
            "AccountId": "0015e000009T7JhAAK",
            "Id": "0035e000006WP0LAAW",
            "Name": "Andy Young"
        }]
    }
}

Execute Anonymous Code

RestRequest request = new RestRequest();
request.requestUri =
    'https://cunning-fox-unspoh-dev-ed.my.salesforce.com/services/apexrest/Account/0015e000009T7JhAAK/contacts';
request.httpMethod = 'GET';
request.params.put('status', 'Working');
RestContext.request = request;

Account a = AccountManager.getAccount();

system.debug('a: '+a);

Results from both Execute Anonymous and Test Class

Account:{Id=0015e000009T7JhAAK, Name=Dickenson plc}
2
  • 2
    Speaking to your running this through Execute Anonymous... Part of this is just an artifact of how System.debug() serializes objects. If you'd put a.Contacts into that debug, it would show you the contacts. Of course, that's assuming that there are, in fact, Contact records related to your chosen account.
    – Derek F
    Commented Jun 10, 2021 at 17:01
  • 1

2 Answers 2

2

This appears to be an issue with your url mapping.

Your class uses /Account/*/contacts
Trailhead specifies that your mapping should be /Accounts/*/contacts (notice that the requested mapping uses "Accounts", plural)

Beyond that, using @isTest(seeAllData = true) is bad practice, and you should nip that in the bud before it becomes a habit. In 99.9% of cases, you should be creating all of the test data you need inside of the test class. If you had done that in this case (creating both an Account and a Contact, and associating the test Contact to the test Account, at the very least), you could rule out the possibility that your Account had no related Contacts.

2
  • 99.9% is probably actually a significant undercount. One in a thousand is probably higher than the rate I have observed, at least.
    – Adrian Larson
    Commented Jun 10, 2021 at 17:13
  • Thanks so much @DerekF, that did indeed solve the problem. Also, I agree with the seeAllData argument - I normally only use it when it is required (e.g. testing chatter message functionality), but I was being lazy since this was a trailhead sandbox ; P
    – Jwok
    Commented Jun 10, 2021 at 17:44
0

Derek F's answer was correct (and marked as the accepted answer). For future readers, here was my final code:

Apex Class:

@RestResource(urlMapping='/Accounts/*/contacts')
global with sharing class AccountManager {
    @HttpGet
    global static Account getAccount(){
        RestRequest request = RestContext.request;
        String accountId = request.requestURI.substringBetween('Accounts/', '/contacts');        
        Account result = [SELECT Id, Name, (SELECT Id, Name FROM Contacts) FROM Account WHERE Id=:accountId];
        system.debug('result: '+result);        
        return result;
    }
}

Apex Test Class

@isTest
public class AccountManagerTest {

    @isTest(SeeAllData=true)
    public static void testGetAccount(){
        
        Account a = [SELECT Id, Name, (SELECT Id, Name FROM Contacts) FROM Account LIMIT 1];
        // Set up a test request
        RestRequest request = new RestRequest();
        // Set request properties
        request.requestUri = 'https://cunning-fox-unspoh-dev-ed.my.salesforce.com/services/apexrest/Accounts/'+a.Id+'/contacts';
        request.httpMethod = 'GET';
        // Finally, assign the request to RestContext if used
        RestContext.request = request;

        Account result = AccountManager.getAccount();
        system.assertEquals(a.Id, result.Id);
        system.assert(result.Contacts.size() > 0);
        system.assertEquals(a.Contacts, result.Contacts);
    }
    
}

Note: As stated by Derek F, using "seeAllData" in this fashion for testing is bad practice. Creating test data is more appropriate, such as the following code provided by Kritik in response to the question Apex REST Services Challenge - Unclear on return format.

@isTest
private class AccountManagerTest { 
    @isTest static void testAccountManager() {
        Id recordId = createTestRecord();

        Contact Cont1 = new Contact();
        Cont1.LastName = 'ABC';
        Cont1.AccountId = recordId ;
        insert Cont1;

        Contact Cont2 = new Contact();
        Cont2.LastName = 'XYZ';
        Cont2.AccountId = recordId ;
        insert Cont2 ;

        RestRequest request = new RestRequest();
        request.requestUri =
            'https://{baseurl}/services/apexrest/Accounts/'+recordId+'/contacts';
        request.httpMethod = 'GET';
        RestContext.request = request;
       Account lst = AccountManager.getAccount();
       /* Tried to return Contact List but failed Trailhead Verification
       List<Contact> lst = new List<Contact>();
       lst = AccountManager.getAccount();
       */
    }

    static Id createTestRecord() {
        Account accTest = new Account(Name='Test Record');
        insert accTest ;
        return accTest.Id;
    }          

}

You must log in to answer this question.

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