3

So I am writing an API client for some third-party service. I get a response as JSON. now I am getting 100 fields as JSON responses but am consuming only 3 of those columns. I am using strict parsing using JSON.deserialize and deserializing all those 100 fields into apex class member variables for future-proofing it.

Now while writing test classes I need to cover them. Is there Describe Fields equivalent for apex class that I can use to cover those unused columns in test class? It seems quite hectic to just copy-paste 100 lines just to show class variable is being used. I don't wanna use JSON.derializeUntyped as it's not strict parsing which I need. What is the best way to increase coverage for those unused JSON columns for now?

JSON2ApexClass:

public class ResponseWrapper{ 
    @AuraEnabled public Balances balances { get; set; } //Used
    @AuraEnabled public Boolean allowOffset { get; set; }  //Used
    @AuraEnabled public Decimal arrearsTolerancePeriod { get; set; } //Not Used
    @AuraEnabled public Decimal accruedPenalty { get; set; } //NotUsed
   .
   .
//100 such fields

}
5
  • 3
    Apex classes do not support reflection, which is what you are after. You will have to do this using brute force or writing scripting that does test code generation, possibly via JSON serialisation that includes null attributes.
    – Phil W
    Nov 20 '21 at 20:23
  • I had a gut feeling about this. Will keep it open in case someone else already has written some script for test class generation for this scenario Nov 20 '21 at 20:24
  • salesforce.stackexchange.com/questions/135891/… I found helpful answer of @Keith C to this question. It may be useful for your case. Nov 20 '21 at 21:27
  • 1
    Unfortunately that doesn't help with coverage in a test...
    – Phil W
    Nov 20 '21 at 22:32
  • I had a similar case and I guess I removed the getters and setters for unused variables to achieve enough coverage. Nov 23 '21 at 18:39
3

There isn't one. There's no standard "reflection" available, so you can't just dynamically access the properties. Either deal with having a huge copy-paste class, or lose the requirement for "strict" validation. It's rarely useful in most practical cases, although that obviously depends on your risk assessment model for unexpected changes.

2
  • 1
    I quite disagree over your statement around strict validation - without strict validation you cannot detect a typo in a property name that leads to a complete loss of fidelity in the resulting data and its use. Using strict parsing makes 100% sense to me, even with availability of a JSON schema (which, in all honesty, also bug the hell out of me with their inability to be quickly and simply defined as strict).
    – Phil W
    Nov 21 '21 at 6:50
  • 1
    @PhilW As I said, it's about risk assessment. I have been in roles where a typo would have not been able to get to production; we had synchronized release dates across teams, code reviews, code freezes, automated testing, QA and UAT. I've also been in roles where strict validation would have been preferable. That said, strict validation is often a matter of opinion. However, at least for Apex, if you want it, you have to be willing to deal with the consequences (e.g. code coverage).
    – sfdcfox
    Nov 21 '21 at 13:20
1

You can do this trick: create an instance of your class, serialize it and deserialize back to Map. After that you'll have all your fields as keys.

Now it is possible to generate test class (because you have mentioned in the comments, that is it acceptable for you):

ResponseWrapper rw = new ResponseWrapper();
String serializedRw = JSON.serialize(rw);
Map<String, Object> rwMap = (Map<String, Object>) JSON.deserializeUntyped(serializedRw);

String classInstanceName = 'rw';
String className = ResponseWrapper.class.getName();
String testClassBody = '@IsTest\n' 
        + 'private class '+ className + 'Test {\n' 
        + '\t@IsTest\n' 
        + '\tstatic void testMemberVariables(){\n' 
        + '\t\t' + className + ' ' + classInstanceName + ' = new ' + className + '();\n';

for (String memberVariable : rwMap.keySet()) {
    testClassBody += '\t\tSystem.assertEquals(null, ' + classInstanceName + '.' + memberVariable + ');\n';
}

testClassBody += '\t}\n' 
        + '}\n';

System.debug(testClassBody);

HttpRequest req = new HttpRequest();
req.setEndpoint(RestUtils.getSalesforceDomainURL() + '/services/data/v53.0/sobjects/ApexClass');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
req.setBody(JSON.serialize(new Map<String, String>{
        'Name' => className + 'Test',
        'Body' => testClassBody
}));
Http http = new Http();
HttpResponse res = http.send(req);
res = http.send(req);
1
  • 2
    It would be better to have separate test methods per member. I didn't post a similar solution because there is a need to consider: 1. Is the attribute publicly gettable/settable? 2. Is the attribute nullable? 3. What's the type of the attribute to allow non-null setting in the test. Fundamentally you can only support a narrow subset of cases in a code generation manner without actually parsing the Apex itself.
    – Phil W
    Nov 21 '21 at 9:48

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.