Monday, October 24, 2011

Explore the Salesforce REST API using Apigee

The Apigee API console is a useful online tool for testing Salesforce REST API calls including handling the initial oauth log in. It's a great alternative to using CURL.

Tuesday, October 11, 2011

Inserting an Attachment in a Apex test method

A quick Apex code snippet for inserting an Attachment for use in a testMethod. This can be useful to ensure sufficient code coverage in classes that depend on existing attachments.

    /**
     * Create a testing attachment for the opportunity
     */
    private static void addAttachmentToParent(Id parentId) {
    	Blob b = Blob.valueOf('Test Data');
    	
    	Attachment attachment = new Attachment();
    	attachment.ParentId = parentId;
    	attachment.Name = 'Test Attachment for Parent';
    	attachment.Body = b;
    	
    	insert(attachment);
    }

Viewing Apex code coverage

There are several ways to view the code coverage results from running the Apex test cases.

  • With the Eclipse Force.com integration Code Coverage Results
    This isn't ideal as you have to go from line to line and can't get an overall view of which lines aren't being tested.
  • Apex Test Result page (https://xyz.salesforce.com/setup/build/runAllApexTests.apexp)
    After running the Apex tests from within Salesforce clicking the "Class Code Coverage" "Coverage %" column will bring up a code coverage overlay.
  • The viewCodeCoverage page
    This is my current preferred approach. From the Apex Classes page (https://xys.salesforce.com/01p) click the percentage in the "Code Coverage" column. This will bring up a new page with the Apex Class ID in the query string: https://xyz.salesforce.com/setup/build/viewCodeCoverage.apexp?id=01pE00000008cZt
    One advantage of this page is that you get a drop down at the top of the page that you can use to examine each test case in isolation.
    Also, you can keep it open in a separate browser tab and just refresh it as required.

See Also:

  • At the time of writing there was a super frustrating bug in the Salesforce Code Coverage System. Eclipse would tell me the code coverage was one value, say 79% for the class in question, and the Apex Classes page would only have 61% in the Code Coverage column. The code coverage views above would show missing coverage on comments and closing braces. After some hunting around I found you must clear the Apex Test Results History (https://cs8.salesforce.com/07M, replacing cs8 with your Salesforce server instance). After which the two figures would line up again.

Friday, October 7, 2011

SharpZipLib "Cannot access a closed file" when extracting file stream

Another Friday afternoon riddle. Using ICSharpCode.SharpZipLib to extract a file from a zip via a Stream. The process works fine in a debug build but fails in a release build with the following exception:

System.ObjectDisposedException: Cannot access a closed file.
   at System.IO.FileStream.Seek(Int64 offset, SeekOrigin origin)
   at ICSharpCode.SharpZipLib.Zip.ZipFile.PartialInputStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputBuffer.Fill()
   at ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream.Fill()
   at ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at xyz.DataExport.Extractor.CopyStream(Stream input, Stream output) in D:\xyz\DataExport\Extractor.cs:line 172

The offending code:

		public Stream GetEntityData(string sourceCsvName)
		{
			// Scan the Zip files looking for the required entity.
			DirectoryInfo dirInfo = new DirectoryInfo(csvBaseDirectory);
			foreach (FileInfo fileInfo in dirInfo.GetFiles("*.zip"))
			{
				Logging.InfoFormat(this, "Searching Zip {0} for {1}.csv", fileInfo, sourceCsvName);

				ZipFile zf = new ZipFile(fileInfo.FullName);
				ZipEntry ze = zf.GetEntry(sourceCsvName + ".csv");
				if (ze != null)
				{
					Stream entryStream = zf.GetInputStream(ze);
					Logging.Info(this, "Opened input zip Stream from: " + fileInfo);

					// Had issues with the csvInputStream closing during processing. Copy into memory
					MemoryStream storeStream = new MemoryStream();
					CopyStream(entryStream, storeStream);
					storeStream.Flush();
					Logging.Info(this, "Closing input zip Stream from: " + fileInfo);
					entryStream.Close();
                                        // Fix by uncommenting the following line
                                        //zf.Close();

					storeStream.Position = 0;

					return storeStream;
				}

			}

			return null;
		}

I suspect that in a release build the garbage collector is being more aggressive efficient and disposing of the ZipFile zf almost immediately which is causing the Stream to close before it can be copied to the MemoryStream.

This seems to concur with the fix I did by explicitly calling zf.Close() after entryStream.Close().
In debug mode the garbage collector was disposing of the ZipFile later on, which is why I had already added code to copy the Zip stream into a MemoryStream.

Wednesday, October 5, 2011

Viewing Apex Callout Request and Response XML

A Salesforce Sandbox doesn't currently provide the same level of Callout SOAP tracing that a development environment does. I've found that by using SOAP UI I can easily create a Mock Service from the WSDL and redirect the Salesforce Endpoint there to see the outbound SOAP messages.

Before you read any further. Go to Salesforce Ideas: View Callout Request and Response XML in Sandbox and promote it so that Salesforce can extend the Sandbox to include the callout xml.

Steps to trace Salesforce Callout SOAP with SOAP UI

  • Save the WSDL and import it into Salesforce
  • Add the mock service as a remote site in Salesforce
  • Change the endpoint_x in Apex to the mock service URL

Monday, October 3, 2011

Salesforce Force.com Quick Access Menu

Just came across the new "Force.com Quick Access Menu" that appears collapsed on the top right of the screen. It's coming in the Winter '12 Force.com Platform Release. You could also check out the Online Help.

See Also: