1

I'm getting error when trying to send visualforce page as email attachment.

public with sharing class PdfEmailController {

 // public ID PAId {get;set;}
 // public String email {get;set;}

  public static void sendPdf(String PAId,String email) { // email needs to be fixed to encompass PM

    PageReference pdf = Page.PA_Detail;
  //PageReference pdf = Page.success;
    // add parent id to the parameters for standardcontroller
    pdf.getParameters().put('id',PAId);
    pdf.setRedirect(true);
    // the contents of the attachment from the pdf
    Blob body;

    try {

      // returns the output of the page as a PDF
      body = pdf.getContent();

    // need to pass unit test -- current bug  
    } catch (Exception e) {
        System.debug('Exception'+e);
    //body = Blob.valueOf('Some Text');
    }

    Messaging.EmailFileAttachment attach = new Messaging.EmailFileAttachment();
    //attach.contentType('application/pdf');
    attach.setFileName(PAId + '.pdf');
   // attach.setInline(false);
    attach.setBody(body);

    Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
    mail.setToAddresses(new String[] { email });
    mail.setSubject('PA has been Approved');
    mail.setHtmlBody('Here is the copy of approved PA! Check the attachment!');
    mail.setFileAttachments(new Messaging.EmailFileAttachment[] { attach }); 

    // Send the email
    Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });

    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'Email with PDF sent to '+email));

  //  return null;

  }

}
3

3 Answers 3

2

Another way around this if you don't want to put Test.isRunningTest() in your production code is to use an interface defined in the PROD PdfEmailController class

public interface IPdfContent {
  public Blob getContent(PageReference pg);
}

with two implementations - one coded in your PROD PdfEmailController class, one coded in your Test Class

In the PROD class:

public class ProdPdfContent implements IPdfContent {
  public Blob getContent(PageReference pg) {
    return pg.getContent();
  }
}

In the test class:

public class TestPdfContent implements PdfEmailController.IPdfContent {
  public Blob getContent(PageReference pg) {
    return Blob.valueOf('foo');
  }
}

In the PdfEmailController class, add a variable that is initialized at instantiation:

public  IPdfContent iPdfContent = new ProdPdfContent();

In the testmethod after constructing PdfEmailController, set the value of the variable referencing the interface to the concrete implementation in the testClass

PdfEmailController ctlr = new PdfEmailController();
ctlr.iPdfContent = new TestPdfContent();
ctlr.sendPdf(..);

and finally, replace your try-catch block with

body = this.ipdfContent.getContent(pdf);

When running in the test context, the test interface's method getContent returns a constant blob that you can assert for.

When running in the PROD context, the real SFDC getContent()method is invoked by the PROD implementation of the interface that uses your actual page reference.

I also assumed in the above that the method sendPdf() is made an instance method (not static) but you could rework my example to use statics

There may be syntax errors above (was typed in)

4
  • An interface, two classes implementing that interface, a field (better @TestVisible than public?). Maybe a little OTT compared to an if/else? And as for the I prefix for interfaces...
    – Keith C
    Jul 7, 2015 at 23:51
  • Woops linked to something that I think concluded to have the I prefix (but a bit hard to tell). But I'm for Dont Distinguish Between Classes And Interfaces.
    – Keith C
    Jul 8, 2015 at 0:01
  • @keithC - I wasn't really disagreeing with your approach, merely suggesting an alternative (and I agree, OTT) and perhaps more suited to more complex mocking use cases (I've used it to deal with inbound email handler responses). The I prefix for interfaces I borrowed (I think) from Andrew Fawcett's Apex wrapper for Metadata API and like it for doc reasons especially since I came late to using apex interfaces. I'll admit that SFDC doesn't use the I and I generally try to model my conventions after SFDC. Thanks for linking to the article
    – cropredy
    Jul 8, 2015 at 17:15
  • My comment was late at night and not meant to be any kind of dig. And the I thing will forever split opinion - the main thing is you have good reasons for your choice.
    – Keith C
    Jul 8, 2015 at 17:33
1

A possible cause of your problem is that pdf.getContent() is throwing an exception that your try/catch is hiding and leaving blob null.

If it is necessary to have the try/catch for a test case use this pattern:

Blob body;
if (!Test.isRunningTest()) {
    body = pdf.getContent();
} else {
    try {
        body = pdf.getContent();
    } catch (Exception e) {
        // Expected in test
    }
}

so that in the non-test situation any unexpected error is propagated and can be detected and provides the underlying cause of the problem.

2
  • Actually I'm trying to call the PdfEmailController from a trigger may be that is cause of problem since after going through the link I noticed getcontent cannot be used in trigger. If this is the case how to send the email attachment from a trigger?? Jul 8, 2015 at 13:28
  • @user1585871 Yeah I thing calling in that context causes an exception with a "Getting content from within triggers is currently not supported." message to be thrown.
    – Keith C
    Jul 8, 2015 at 13:31
0

For those who follow same issue

Here is some background and solution:

If we are talking about sending email with visual force page as attachment in Trigger it would be problem. Trigger does not support getContent() method of PageReference class. If we are thinking to use getContent() in future call then again we are not lucky enough, because @future methods does not support it. Also Apex job doesn’t support this method.

So we are in a grave situation. But thank God there is a workaround Solution is very simple, We will expose apex method as a REST API. Code for sending email will be written in APEX based REST API and our Trigger will call this method asynchronously using @future annotation.

Please follow example in following link:

http://www.jitendrazaa.com/blog/salesforce/apex/send-email-with-generated-pdf-as-attachment-from-trigger/

You must log in to answer this question.

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