Apex Testing Error: System.QueryException: List has no rows for assignment to SObject

apexemailqueryexceptionsobjectunit-test

I am trying to get my test data set up for an Apex test class I made and I am getting "System.QueryException: List has no rows for assignment to SObject" in the tests error log. Not really sure what I am missing and any help would be appreciated! The error is occuring at "StaffEmailToolHelper.sendEmails(fiList);" in the test class

StaffEmailHelperTool.apxc

    
    @InvocableMethod(label='Staff Email Tool Helper')
    global static void sendEmails(List<FlowInputs> requests) {      
        String emailTemplate = requests[0].emailTemplate;
        String senderEmail = requests[0].senderEmail;
        String senderName = requests[0].senderName;
        String whatID = requests[0].whatID;
        String selectedContacts = requests[0].selectedContacts;
        
        List<Id> splitContacts = selectedContacts.split(';');
        
        EmailTemplate template = (EmailTemplate)[SELECT Id, Subject, Body, HTMLValue 
                                     FROM EmailTemplate 
                                     WHERE Name LIKE :emailTemplate LIMIT 1];      
        
        //assigns org-wide email
        OrgWideEmailAddress[] owea = [SELECT Id
                                      FROM OrgWideEmailAddress
                                      WHERE Address =: senderEmail];

        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setWhatId(whatID);
        
        mail.setTemplateId(template.Id);
        mail.setHtmlBody(template.HtmlValue);
        mail.setTreatBodiesAsTemplate(true);
        mail.setSubject(template.Subject);
        if ( owea.size() > 0 ) {
            mail.setOrgWideEmailAddressId(owea.get(0).Id);
        }
        
        //Loops through contact Ids and sends emails using current contacts info
        for (Integer i=0; i<splitContacts.size();i++){
            String contactID = splitContacts[i];
            mail.setTargetObjectId(contactID);
            List<Contact> contactEmailList = [SELECT Email FROM Contact WHERE Id =:contactId LIMIT 1];
            String contactEmail = contactEmailList[0].Email;
            mail.setToAddresses(new String[] {contactEmail} );    
            Messaging.SendEmailResult [] r = Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail});
        }
        
    }
    
    global class FlowInputs{
        
        @InvocableVariable
        global String emailTemplate;
        
        @InvocableVariable
        global String senderEmail;
        
        @InvocableVariable
        global String senderName;
        
        @InvocableVariable
        global String whatID;
        
        @InvocableVariable
        global String selectedContacts;
    }

}

TestStaffEmailHelperTool.apxc

private class TestStaffEmailToolHelper {
    @IsTest
    static void testEmailSent(){
        StaffEmailToolHelper.FlowInputs fi = new StaffEmailToolHelper.FlowInputs();
        fi.emailTemplate = '00X1R000001ZvyfUAC';
        fi.selectedContacts = '0037g00000vzoPwAAI;0037g00000x8zAcAAI';
        fi.senderEmail = '[email protected]';
        fi.senderName = 'example';
        fi.whatID = '0017g00001Phrc9AAB';
        
        List<StaffEmailToolHelper.FlowInputs> fiList = new List<StaffEmailToolHelper.FlowInputs>();
        fiList.add(fi);
        
        Test.startTest();
        StaffEmailToolHelper.sendEmails(fiList);
        Test.stopTest();
    }

}

Best Answer

tl;dr

Your issue is that your test is setting an Id for the emailTemplate, whereas your query is looking for the name of the emailTemplate.

long version

SOQL queries always return a List<SObject> (or a List<AggregateResult> if your query includes the GROUP BY clause).

Salesforce has some syntactic sugar that allows us to stick the result of a query into a single SObject instance, but only when the query returns exactly 1 row. If your query returns 0 rows, you get the "System.QueryException: List has no rows for assignment to SObject" error that you're running into. (You'd get a similar, but different, error if your query returned more than 1 row).

Hard-coding Ids in a test (or really anywhere) is a red flag. Doing so has a tendency to cause test failures in other orgs (i.e. if you create a record in a sandbox and hard-code that Id in your test, it'll fail when you try to deploy to production).

When it comes to testing, there really aren't many shortcuts in setting up the test data. You're going to need to create your own test Contact records, your own test Account record, etc...

The EmailTemplate SObject is special. It's what's called a setup object, and data for those are available in unit tests. You still don't want to query based on the Id though (and the Id and Name fields are different). Querying it based on the template name is better.

I believe best practice would be to define a custom label to hold the template name (and then use that in your test like Label.DefaultStaffEmailTemplate), which would allow you to make any necessary changes without the need to do a deployment.

great, so what do I do?

  • Create a test account and test contacts in your test class (and also give your test class the @isTest annotation). Creating an @testSetup method to handle that is best practice, though it does mean you'll need to query to get the Ids in your test method
  • Find the Name of your email template, and use that instead of the Id in your test

Other things to consider:

  • You don't need to cast the result of your EmailTemplate query as an EmailTemplate (so that (EmailTemplate) in front of your query can be removed)
  • If you aren't expecting to handle partial template names (with wildcard(s), e.g. 'myEmailTemp%'), then using Name = :emailTemplate is probably preferable to using Name LIKE :emailTemplate (mostly code-style, I think. Follows the Principle of Least Surprise)
Related Topic