Thursday, 18 September 2014

Bristol DUG Report - Enterprise Design Patterns and Salesforce Dev Quiz!

On the 17th September, the latest Bristol DUG meetup was held in the Elephant Pub in Bristol. In this edition, Andrew Fawcett, CTO of Financial Force, gave a great talk on Enterprise Patterns and the best techniques to use to keep your code organised. The talk contained exerpts from his forthcoming presentations on the subject at Dreamforce 2014, where he demonstrates the improvements to maintainability and adaptability of code which is divided by concern.

Check out the Preview of Advanced Apex Enterprise Patterns Session post on Andrew's blog to get a flavour of the content.


Following the talk, and a recharge of glasses, the first annual Bristol DUG Pub quiz was held. Entrants were treated to 20 quick fire Salesforce development questions (found below, just in case you want to run a similar event at your own DUG). The eventual winner was Alex Tennant, with an impressive score of 16.5!


  1. Q: What does DML stand for?
A: Data Manipulation Language

  1. Q: Name the four types of action you can do as the result of workflow
A: Task, Field Update, Outbound Message, Email Alert.

  1. Q: What number API version is Winter ‘15?
A: 32

  1. Q: Name four programming languages that heroku supports (A cloud trivia favorite of mine)
A: Ruby, Java, Node.js, Scala, Clojure, Python and PHP, Perl (Undocumented).

  1. Q: Name two of the Visualforce tags that belong to the chatter group (IE Chatter: instead of Apex: , not ChatterAnswers:)?
A: feed, feedWithFollowers, follow, followers, newsfeed, userPhotoUpload

  1. Q: Governor limit maths: what is the Total number of methods with the future annotation allowed per Apex invocation x Total number of DML statements issued - Total number of sendEmail methods allowed
A: 10 x 150 - 10 = 1490

  1. Q: True or False, You can deploy a maximum of 5,000 files as part of an individual change set?
A: True

  1. Q: Name the Salesforce API that you can use to build custom development tools for Force.com applications.
A: Tooling API

  1. Q: In the setup menu, does “Static Resources” appear under the “Develop” or the “Create” sub menu?
A: Develop

  1. Q: Name 5 types of charts that can be used in reports and dashboards?
A: Bar Charts, Column Charts, Line Charts, Pie Charts, Donut Charts, Funnel Charts, Scatter Charts.

  1. Q: Who is headlining the Dreamforce gala this year (2014)?
A: Bruno Mars

  1. Q: Which of these is not a standard salesforce object accessible via SOQL - AccountTag , Question or CaseEscalationRule?
A: CaseEscalationRule

  1. Q: In the order of execution, what occurs first, escalation rules or assignment rules?
A: assignment rules

  1. Q: What is the former name of Salesforce Tower in London, which resides on Bishops Gate (bonus point for correctly guessing the number portion of the address)?
A: Heron Tower (Bonus point for number 110 Bishops Gate)

  1. Q: True or False, The salesforce Advanced Developer certification is a prerequisite for taking the technical architect certification?
A: False, you can just do developer

  1. Q: According to trust.salesforce.com, How many EMEA instances (ie EU servers) do salesforce currently host?
A: 4 (EU0, EU1, EU2 and EU3)

  1. Q: When using batch Apex, you must write an Apex class that implements what Salesforce-provided interface?
A: Database.Batchable

  1. Q: Name the two different types (or varieties) of custom setting objects that be created
A: List + Hierarchy

  1. Q: Name the three types of Salesforce collections in Apex
A: List, Set, Map

  1. Q: Name 3 of the annotations supported in apex classes (hint, they all begin with the @ symbol)
A: @Deprecated, @Future, @IsTest, @ReadOnly, @RemoteAction, @TestVisible, @RestResource, @HttpDelete, @HttpGet, @HttpPatch, @HttpPost, @HttpPut


Thanks to all who came along, see you all next time, where we will no doubt have some Dreamforce content! Possibly a report back from the $1Million Hackathon



Sunday, 31 August 2014

Woo Hoo! I'm going to Dreamforce 2014, here's 5 reasons why you should too!

In case you've been living under a rock, you are probably already aware that Dreamforce is the hottest Salesforce event of the year. Every year Salesforce experts and enthusiasts take over San Francisco for a full 4 day conference. Every year the event get's bigger and better, with more breakthroughs, key announcments and cool platform features emerging.

I consider myself very lucky (and thank my employers Desynit) for letting me go to Dreamforce 4 years in a row now. I have previously written a blog post on my trip to Dreamforce 2013, so check it out if you want flavour of what the conference experience is like. But enough about the past, here are 5 top reasons why you should grab a flight to San Francisco and attend this great event this year:

1) The Keynotes

Every year, Salesforce CEO Marc Benioff delivers a massive keynote to tens of thousands of attendees under the Moscone outlining the direction Salesforce is taking as a company, and the enhancements they are making to their product range. The session is always full of interesting case studies regarding companies who are currently using Salesforce to push the boundaries of what is possible.The keynote also highlights the Philantropic efforts the company makes as a whole, and being in such a massive room, you geniunely feel like part of something great.



But it's not all about the Benioff mega show, for the Developers, the Developer keynote provides the community with a view into what the future holds for the Force.com platform, Heroku and APIs. This session always includes some very cool demos of some of the latest features/tools. If you want to attend this session, better be quick on your feet, space is more limited than the main keynote, and it always fills up. I've even known people to be leaning against the theatre entrance door to try and hear the presentaion!

2) The Expert Lead Sessions

Got a problem you just can't get around in your Salesforce implementation? Want to know more about a new feature? Want to learn how to master an existing feature? Then the break out sessions are here to save the day. Dreamforce offers an every increasing array of excellent content from product managers and community experts. Here is the full list of  Dreamforce 2014 sessions .


<Shameless Session Plugs>While we are on the subject, there are two sessions you should consider attending this year. In Clicks vs. Code: Deciding how to build, Simon Lawrence and I will debating the benefits and drawbacks of declarative and programmatical approaches to create functionality within Salesforce. Also, if you are a developer and are considering taking some certification exams, or want some tips on how to successfully pass, you can hear my full personal journey through the certification process in my session called Salseforce Certification - A Developer Journey </Shameless Session Plugs>

3) Hands on Training

It's scientifically proven that people learn more effectively when they experience something first hand, rather than just being told about it. Dreamforce offer several expert led hands on sessions and mini workshops, where attendees can be guided through anything from building their first app, to best testing and batch processing code techniques. Any questions? One of the session assistants will be on hand to answer any queries or problems you have. I have attended these sessions in previous years, and I still refer to the some of the lessons I learned on a daily basis, I can't recommend them highly enough.

4) The Devzone

I maybe be *slightly* biased being a Force.com developer, but honestly the best thing for me about Dreamforce is always the Devzone. This is a slice of dev paradise, with constant developer sessions, hands-on workshops, mini hack challenges, and product demos all around. Everyone in the Devzone is really helpful and enthusiastic, I challenge anyone to enter the Devzone with a Salesforce development question and not have it answered by the time they leave. Just one thing though, remember to leave space in your suitcase for the crazy amount of free training books and swag you will want to bring back with you!



5) The "Dreamforce Glow" effect

Every year after attending Dreamforce, every attendee I have ever known has always returned to their office positively buzzing. The new skills you will have gained, contacts you have made and the sheer scale of the keynotes always gives you a sense of great positivity for the future and raring to start clicking and coding in the cloud all over again.

Any time lost or cost accrued in attending the event is quickly made up for by the new efficiencies you find in the way you work, and the found again mega-enthusiasm you will have for what you do.


Soooo, shall I see you there?

Thursday, 21 August 2014

Bristol DUG Report - Accident Alert: A Summer of Hacks Story

Last night, at the Llandoger Trow in Bristol, the Bristol Salesforce Develoer User Group met up for another chance to chat code and talk about development on the Force.com platform.


The central theme of this latest event was hackathons, specifically the Summer of Hacks London event run by Salesforce last month. A presentation was given by the local Bristol team from Desynit behind the winning entry, an app called "Accident Alert". The app built at the hackathon used the Salesforce1 platform to provide users with the ability to record potential health and safety hazards around the workplace.

The open panel discussion included the three core members of the team (Matthew Morris, Shaun Holmes and myself), and covered how the team went about building the application, how we stayed sane through all night development sessions (coffee and chicken burgers),  and the lessons we learned about good application development.



This presentation wasn't just about the winning entry though, it was primarily about the experience of participating in hackathons, why they rock, the amazing people who attend and how everyone should sign up to them in the future. It's not all about technical expertise and being able to write code, it's all about identifying a potential new application or tool that is of genuine use to Salesforce users, and creating a prototype to fit that bill.

Here is the slide deck from the event:



Continuing on the theme of hacks and development, we were also honored to have a special project presentation by our youngest ever presenter Dara Morris, who presented a highly addictive jet fighter game he recently developed at a hack event. It seems the future of development is bright in the Bristol area!



Want to get involved and attend the Bristol DUG? Then check out the Bristol DUG Meetup page.

Not in the South West of the UK? Then check out the full list of Salesforce Developer User Groups.

Still no luck? Then start your own, add a comment to this post if you want to find out how!

Saturday, 19 July 2014

Summer '14 Release: 7 Force.com Development need to knows!

Summer is officially here. The sun has his hat on, the Pimms is flowing and the BBQs are firing up! Also just like each Summer, Salesforce is making one of its three annual releases. In Summer '14, we see the introduction of extensions to user permissions, a massive increase in API calls, and the ability to handle much larger files as attachments on Objects and e-mails.

The training material for Force.com (for those looking to pass their maintainence exam or find out more about the release) is pretty quick, lasting just a shade over two and half minutes. Viewing this alone, you may be tricked into thinking the only change of note is to user access permissions.

What I want to do in this blog post is dig deeper, way down into the release notes, and demonstrate the not so well known features that are going to be introduced as part of this latest release, that make things possible on the platform for the first time, and can alleviate previous restrictions. I have also included a few new features that you need to be aware of that may impact your org in the future.

So without further ado, here are my 7 need to know features in Summer '14 release:

1) Light vs. Enterprise Objects

As part of Summer '14, when you create any new objects, you will be asked as part of the initial configuration, a series of questions about the your new object:

Allow Sharing (checkbox)
Allow Bulk API Access (checkbox)
Allow Streaming API Access (checkbox)

These settings are all or nothing. If all of the objects are selected, your object will be classified as an "Enterprise" object, if you uncheck all, it will be classified as a "Light" Object. All objects created prior to Summer '14 will be classed as "Enterprise" objects.

So ok that's nice, but why is that relevant you ask? Why would you ever choose a "Light" Object? (apart from if have very selfish users :-D ) . Well this actually all comes down to Licence arrangements for your org. Since Spring '13, The Force.com light licences have been available. There isn't much information about these licences out there, but thankfully Shell Black has a great post explaining licenses.

Basically, if in your org certain users cannot see types of custom object, checking if it is a "Light" object is probably a very good place to start.

2) Increased Maximum Length for Long Text and Rich Text Fields

Long text fields can now be edited to be four times as big in size, 128KB rather than 32KB (or 131,072 rather than 32768 characters). A small change but will free up some implementation concerns, so now your users can ramble to your hearts content!

3) Increased Maximum Number of Relationship and External ID Fields Available Per Object

The number of relationships (lookups/master-details etc.) per object has been increased from 25 to 40, particularly useful if you have complex data models (though in all honesty if you do need all 40 relationship fields, you may want to review your data model!).

Also, the number of fields that can be labelled as External Ids, useful for uploading records from other connected systems with their own unique referencing models, has been increased from 3 to 7, so its now even easier to upsert your existing system data.

4) Full File Search Now Available in the Developer Console

For me, one of the benefits of using an external IDE over the developer console has been the ability to quickly identify search your entire codebase for a particular tag or method, using a complete file term search. An example is when you have a question like"I'm sure I've used the apex:ActionFunction in this project before, but where?". With Summer '14, a complete file search function has been added. Simply go to Edit > Search in Files in the menu, or use the keyboard shortcut (CTRL + shift + H) to open the dialog, and then enter your search term.



There are currently some usability limitations, for example you cannot use a search term that contains a ":" character (like when searching for an apex tag), and there are only so many results that can be returned in the panel. Despite this, I think this still a really good addition to the developer console.

5) Remote Object Enhancements (Developer Preview)

In Spring '14, a new method of interacting with objects via Javascript was introduced in the form of Remote Objects. Using Remote Objects allows you to modify the data in your org purely at the view level using Visualforce alone, you do not have to define any logic in Apex classes. You can read more about Remote objects in this Remote Objects blog post by Josh Birk

The Summer '14 release extends this new feature further, with the addition of an upsert command to update or insert objects based on if they already exist (just like with SOQL). You can now also order your results on the page using an orderby command, use geo-location fields, and use more complex query conditions (not equal, less than or equals, greater than or equals). These enhancements will make it even easier to replace large chunks of Apex code with lightweight Javascript. Give it a go if you haven't done so already!

6) Unlimited Describe Calls

Describe calls are used in Apex to dynamically access the data model configuration of your org. You can use describe calls, for example, to view the possible values of Picklist fields, or even get all of the fields that belong to an object. I have used these methods successfully in previous posts, especially those regarding cloning objects.

Previously Describe API calls have been limited to 100 per code execution process. In summer '14 this limit has been completely removed, so you can make as many calls as required. A couple of words of warning though, remember that this limit was there originally for a reason, if you are running code that is using the describe method over 100 times, you may want to consider if there is not a more efficient way to get the information you need. Don't get slack in your coding just because the limit is gone :D.

7) Create Price Book Entries in Tests

This is a win for anyone (including myself) who has ever had to create a test relating to price book and price book entries. Before this release, the only way to access a price book to create entries was through the use of the *evil* seeAllData=true flag in your tests. Now, thanks to the new Test.getStandardPricebookId() method, you can access a price book and associate new Price Book Entries without having to access all data.

Here is an example of the method in use, taken from the Summer '14 Release Notes:

@isTest
public class PriceBookTest {
    // Utility method that can be called by Apex tests to 
    // create price book entries.
    static testmethod void addPricebookEntries() {
        // First, set up test price book entries.
        // Insert a test product.
        Product2 prod = new Product2(Name = 'Laptop X200', Family = 'Hardware');
        insert prod;
        
        // Get standard price book ID.
        // This is available irrespective of the state of SeeAllData.
        Id pricebookId = Test.getStandardPricebookId();

        // 1. Insert a price book entry for the standard price book.
        // Standard price book entries require the standard price 
        // book ID we got earlier.
        PricebookEntry standardPrice = new PricebookEntry(
        Pricebook2Id = pricebookId, Product2Id = prod.Id, 
        UnitPrice = 10000, IsActive = true);
        insert standardPrice;

        // Create a custom price book
        Pricebook2 customPB = new Pricebook2(Name='Custom Pricebook', 
                                             isActive=true);
        insert customPB;

        // 2. Insert a price book entry with a custom price.
        PricebookEntry customPrice = new PricebookEntry(
        Pricebook2Id = customPB.Id, Product2Id = prod.Id, 
        UnitPrice = 12000, IsActive = true);
        insert customPrice;

        // Next, perform some tests with your test price book entries.
    }
}

Thursday, 16 January 2014

Force.com Development: 10 things that blew my mind in 2013, and what they mean for 2014

 

2013 was a great year for Force.com developers. Below are my top 10 reasons why 2013 rocked, and what they mean for the year ahead.

1) The Salesforce 1 Platform

What happened in 2013: Of course, no-one could talk about Salesforce in the past year without mentioning the all-in-one mobile ready Salesforce 1 Platform and App. When this was released at Dreamforce, I was amazed at how well executed the launch of the product was, overnight chatter mobile was magically transformed into the Salesforce 1 Mobile app.


What it means for 2014: Everything. A lot of development teams and businesses are changing their model to "mobile first", so the quicker developers become familiar with the 1 platform, the better. In the coming year, Salesforce 1 will be enhanced with new features (safe harbour :p ) and become the first point of org contact for many users. I am really looking forward to getting stuck into building some cool mobile apps that combine the power of the Force.com platform with native mobile features.


Find out more : Salesforce 1 Platform Overview

2) Connected Devices

2013: The year "The internet of things" became "connected customers". We were reminded that behind every device, sensor, and flashing LED is a person with a story. The Connected Devices Lab at Dreamforce was truly inspiring, giving developers a chance to build things that interact with more than a screen and keyboard. I would say that connected devices grew up this year, but I enjoyed playing the fruit piano in the CDL too much to back that up.

2014: I really feel that this year we will begin to see more emergence of practical connected systems. Up until now these systems have been largely conceptual / DIY. Expect more widespread adoption and innovation this year, with real world data directly driving systems and vice versa.

3) Elevation!!

2013: Throughout last year, Salesforce ran several ELEVATE free one day hands-on training events, designed to get people interested in the platform started, and also educating advanced developers on mobile app best practices. In November, Bristol held an ELEVATE event for the UK. I co-ran the beginner track training, and it was a fantastic experience. I was really impressed at how much the local development community embraced Salesforce as a platform, and quickly understood the key concepts and tools. It was great as an experiened Force.com dev to take a step back and appriciate the building blocks of the platform all over again.

2014: There already a number of Elevate events planned for this year, and with a new syllabus that includes the Salesforce 1 Platform, there should be something for everybody at these events. Watch this space, we could be coming to a city near you soon.
Find out more: Any upcoming elevate event details will appear on the Developerforce Calendar.

4) Supercharged hackathons

2013: Last year, Salesforce hackathons hit the big time. The $1,000,000 hackathon offered developers with the opportunity to change their lives by building an application using Force.com. Mobile hack week in April, promoting the mobile capabilities of the platform, linked together the different user groups worldwide. Alongside the already popular mini-hacks, and product specific hacks, there was plenty of hacking to be had.

2014 : Expect a lot more competitions at Salesforce events, with massive participation, and suitably gargantuan prizes. At a guess I would also expect a couple of themed weeks for developer user groups. I am certainly going to be participating (and help run) some hacks this year.

Find out more: The Dreamforce Hackathon

5) Salesforce devs embrace the stack exchange

2013: The Salesforce Stack Exchange is a question and answer community site for all things Force.com. The exchange has picked up a lot of momentum in the past 12 months, now boasting nearly 3500 active members. If you haven't got an account, what are you waiting for? Sign up, sign in, and start asking and answering. The community are really quick to respond to questions, with quality answers.

2014: Even more adoption. More questions, more answers, and more badges being handed out to development community. I do also predict a slight move away from the Salesforce developer forums for developer queries, the stack exchange has a much more natural question and answer format.

6) There's an API for that

2013: There are now 10x more APIs than there were previously available for the platform.

2014: This year there will  be many more apps that take advantage of these extra APIs, and alongside more advanced mobile integration, prepare for more development release/testing apps. The extra exposure the core elements of the platform now have will allow developers to apply greater control over their orgs and development environments.

Find out more: All the API defintions are available through the developer documentation section of developer.force.com

7) Books and knowledge galore

2013: I was lucky enough to be asked to review two great books on the subject of Force.com development. The Salesforce CRM Admin Cookbook by Paul Goodey, which although aimed at administrators, has loads of great tips for developers too, and provided solutions to some of the common problems.
Keir Bowden's Visualforce Development Cookbook is a book I would recommend to any Force.com developer, regardless of experience. If you have just developed your first app, or have been using the platform for 10 years, you will find some incredibly useful tips and advice.

2014: I am hopefully going to be reviewing some more books in the next 12 months. If you are in the process of writing or publishing a Force.com Development / Administration and would like a review, by all means add a comment below.

Find out more: Check out my review of the Salesforce CRM Admin Cookbook, and also have a look at the Visualforce Development Cookbook.

8) Getting certified is a journey

2013: From a personal perspective, A lot of my 2013 was centered around certification. I got my Force.com Administrator certification to go alongside my existing Force.com Developer and Force.com Advanced Developer certifications. But it wasn't just my own certifications that were important, I made it my mission to guide others through the process. I was lucky enough to be part of a panel session at Dreamforce entitiled "I passed the advanced developer certifiicaiton". I also helped 5 members of the Desynit team become certified by passing on my knowledge of the platform.

2014: Certification is always going to be important, it helps you to prove you know your Salesforce. This year, I will be helping to train more developers and administrators, and also publishing via blog/presentations my experiences of the certifications, and my recommendations to anyone undertaking an exam. Plus there is always that little matter of the Technical Architect.

Find out more: Developer Certification

9) Command Line Interface tool

2013: The command line interface (CLI) tool was a major part of the developer keynote at this year’s Dreamforce. As the name suggests, it provides text command line access link to your salesforce org. You can manage objects and configuration, execute SOQL queries and even execute apex code right from a simple prompt. If you want to be able to configure an org quickly, this is the tool to use.

2014: I am really looking forward to getting hands on with the CLI tool this year. I can certainly see it being part of my org intialisation process for new projects, but that is just the start. Speed is the name of the game with the CLI, and I can really see it speeding up a lot of some of the slightly more long-winded configuration processes.

10) The developer console is gaining momentum

2013: This year the developer console became more than a nice side feature that could be used to develop Force.com code, it started to become the number one tool for developing through the web. The console moved much more towards a complete online fully fledged IDE, rather than just a tool for executing anonymous code and debugging.

2014: The developer console will continue to gain more and more traction. More features will be added throughout the year, and it will be faster. Don't be surprised if some of the configuration and development tools start to be removed from the GUI and placed exclusively in the developer console. Of course there is still a major lean by developers to use desktop based editors such as Sublime and Eclipse, but as the console gains more features and speed, heads will definitely start to be turned.

With all these innovations / new tools, 2014 is shaping up to be a great year for Force.com developers. I can't wait to get stuck in.

What are you looking forward to this year? Add a comment below!

Friday, 10 January 2014

Bristol DUG (FxFW) - New Year? New Salesforce Certificate!

On Wednesday 8th January, Bristol held it's first developer user group event of the year. The theme of the event was certification. As we start the new year, many developers are looking for ways to improve their skills over the coming year, and what better way than taking the challenge of gaining some official Salesforce certifciation.

There were two presentations during the night. Fellow Desynit developers Simon Lawrence and
Julio Fernandez (both certified Force.com developers) provided a guide to the Force.com Developer Certifcation. The talk included an explanation of the certifcation format and process, alongside their own personal experiences. Copies of the slides for this presentation and more information about the Force.com Developer Certifcation are available on Simon's Blog, and also Julio's Blog .


Following this, I took to the mic and presented my section of the "I passed the advanced developer certifcation" panel session I co-presented at Dreamforce 2013 . Again this presentation was a combination of background information about the certification process, with some practical advice and insights from my own experiences. A video of the full presentation from Dreamforce can be found on my Dreamforce Diary post.


After the presentations, group discussions began, at first focusing on the certifcation processes and aims for this year, but topics eventually spread and evolved into change sets, single sign-on, the metadata API and preferred development environments amongst others. 


I really enjoyed the event, it was a great chance to catch up with the local Force.com development community. There is a real feeling of enthusiasm for the coming year ahead.

I'm really looking forward to the next Bristol DUG, scheduled for Wednesday 5th March. Richard Donkin, CTO of Cloudfind, is going to providing a session on Salesforce Canvas. So if you fancy learning about integrated external apps or talking Force.com development over a free beer, please sign up to our Meetup group.

Sunday, 1 December 2013

Dreamforce 2013: A developer diary

Last week I was lucky enough to fly over to San Francisco and attend Dreamforce 2013. This was my third consecutive year attending the event, and was the biggest, best and most inspiring yet. Here is a diary of my experience...

Sunday


Despite the fact that Dreamforce did not start officially until Monday, that was not going to halt the community from kicking things off early. Postcode Anywhere hosted the networking event Ale Anywhere, a chance for delegates to get together before the event and discuss the upcoming conference. This was a great night, heavily attended by UK representatives (hence the ale). I'm sure that next year the event will be running again, which I would really recommend for any UK delegates looking to acclimatise themselves before the conference. In the mean time, check out Postcode Anywhere's key DF takeaways .

Monday


When attending Dreamforce in previous years, I have always had a tendency to hit the ground running, and this year was no exception. Not long after the massive devzone was officially opened in the Moscone West, I presented in a session entitled "I passed the Force.com Advanced Developer Certification". It was a panel session including Peter Chittum, Leah McGowan-Hare and Barry Hughes. The aim of the session was to show the varied personal experiences of developers who have successfully passed the certification, and the positive and negative experiences throughout the process. I really enjoyed taking part in the session, I felt that it represented the human element of the certification experience really well.

here is the session recording:


 


If you're interested in more sessions about certifications, there is a similar session for the Technical Architect Certification

After my session, I spent the majority of the day exploring the devzone, visiting partner stalls, attending some cool sessions and getting hands on with some practical exercises. The main difference between the devzone this year and previous years was the emphasis on connected devices in the new "connected devices lab" area. This area was set up to demonstrate how the data we hold in cloud applications can be generated by sensors and be used in turn to affect connected systems physically. All kinds of systems were on display, from large scale 3D printing to a digital piano with bananas and pears as keys.

Tuesday

Tuesday morning was all about one thing, the Dreamforce keynote with Marc Benioff and special guests. As per usual, the keynote was full of inspiring technology and a view of the present and future of Salesforce. The keynote opened with the companies philanthropic efforts, which was a nice touch personally as I am a massive fan of the 1-1-1 model Salesforce endorses. The keynote included a lot of cool demos, not least using Google Glass to update a support case in real time, check it out below, I challenge you not to be inspired.




This year's big announcement was the introduction of the Salesforce 1 Platform. The 1 platform is a culmination and enhancement of all of existing Salesforce functionality into a full mobile ready crm platform. This was a welcome announcement, not least because it was available that day, right then. This wasn't merely a forward looking statement, it was all live now, already my chatter mobile app had been replaced with the new 1 platform mobile app, which included all of my org standard objects and apps. The whole mobile experience for users is now a lot more complete and slick, if you have an org and android/iphone, I suggest you download the mobile app ASAP. 

After the keynote, fully charged with awesome enthusiasm for all things Salesforce and Force.com, I returned to the devzone to take part in some developer mini-hacks. The mini-hacks are a set of coding and development challenges designed to get developers started with some force.com features they may not have experienced before and some cool partner apps.

After a long day of hacking, I managed to finish 4 of the partner hack challenges, set by Docusign, Skuid, HP and Twilio. I particularly enjoyed the Docusign and Twilio challenges, which were about electronically signing important documents from Salesforce objects and sending text messages directly from a Salesforce page respectively. Even better, because I was one of the first 10 developers to finish the challenges, I got presented with some beats audio headphones and a hoodie by the Docusign stand.




Congrats to fellow Desynit developer Simon Lawrence (check out his blog), who was also one of the first ten to complete the challenges!

I finished the day by going to the Dreamforce Gala, which included performances by Blondie and a slightly damp Green Day :) , as well as free beer and all the hotdogs you could ever want. It's a hard life for a dev sometimes ;) .

Wednesday


First call on the third day of Dreamforce was the developer keynote session. The top headline for me was that the total number of Force.com developers had nearly doubled in the past year, jumping from 800,000 to over 1.4 million! Wow that's a big jump, but even so the demand for new Force devs is still higher than ever.

The keynote included a demo of the Command-line Interface tool for force.com, a command line tool for configuring force.com environments. I must admit I was a little skeptical at first, but once I saw the ease of which a new org was set up and field values were added, I was sold, well worth a look.

After the keynote, I ventured to the other end of the conference to attend the "Listen up Marketing! The User Group Program has some valuable lessons to share" session hosted by Desynit's very own Amy Grenham and Matthew Morris. Desynit have had a very active role in the user group program, and this was a great opportunity for us to show the lessons we have learned, and how users groups can adapt to their local environment to make sure attendees get the most out of events. If you are involved in a user group, or have ever contemplated running / attending an event, then this is the session for you.




After some lunch, I returned back to the developer zone, for some further exploration, experimentation and sessions. One of the real highlights was a session titled "The Apex Ten Commandments", hosted by Francis Pindar and Kevin Poorman. The purpose of the session was to outline 10 core best practices for developing on the Force.com platform. This is a great session for new and experienced developers alike, if we could all follow these rules, our own and fellow developer's lives would all be made a lot easier.



Thursday


It was the last day of the conference, but everyone was still buzzing. I started the day by taking one last tour of the devzone and the cloud expos. I had already been at the conference for 3 full days, but still had the feeling I had hardly scratched the surface. I saw a few demonstrations of sensor / actuator tech from Reid Carlberg, who was starting to lose his voice after three solid days of high energy demos (the man is a machine).

Throughout the conference, a $1,000,000 hackathon challenge had been running. One of the last sessions to start in the devzone was a demonstration of the five shortlisted apps to win the grand prize. The range of entries was inspiring, and although I was dubious about the degree to which the solutions had be "hacked", I couldn't help but be impressed by the quality and usability of the varied solutions. The entry that particularly impressed, upshot (the eventual winner), was a natural language instant report generator for Salesforce users on the go. It was simple in terms of development components and interface, but when combined made a simple incredibly usable and effective app. Another noteworthy finalist was Salesfetch, a lead history app that showed the entire interactions the business has had with an individual, not least because it was produced by four French students who had no force.com experience prior to that week.

Friday


I made my way back to the UK having fully enjoyed and been inspired by Dreamforce like never before. The introduction of the 1 platform and the move towards a more connected world of devices and customers were the key elements of the conference, and provide devs with more than enough to get their teeth into for a long time to come, well until at least DF14 anyway ;)

Sunday, 15 September 2013

Clone Plus Test Method Example

Well, it's been a while! I have been very busy, getting qualified as a certified advanced developer (see shiny new badge on the right hand bar :-o), sorting out my upcoming Dreamforce visit, reviewing Force.com books and promoting the South West Force.com group (more posts to come on these topics soon!).

Anyway, I figured a visit to clone plus was well overdue. For those unfamiliar with clone plus and what it does, see these previous posts introducing clone plus and some refinements that have been made along the way. The posts have proved quite popular, with lots of people providing feedback and asking questions. One of the most common questions that has appeared on the topic is test coverage. As is well known, all apex code needs at least 75% coverage to be promoted to a production org, and clone plus is no exception.

Below is a sample test class for clone plus, alongside the latest implementation of the page and controller (I have made some tweaks, mainly error handling). This is just an example of how you might go about testing a class like the clone plus controller. It is by no means an absolute complete testing solution, as it does not cover every scenario the user could find themselves in while using the controller.

Latest Version of Clone Plus Controller:
public class ClonePlusController {

  public List<relatedObjects> objectChildren  { get; set; }
  public String               objectTypeName  { get; set; }
  public String               objectName      { get; set; }
   
  @TestVisible private SObject headSObject, headClone;
  
  // Initialisation method called when the clone plus page is first loaded.
  // Use the id page parameter to find out what kind of object we are trying to clone.
  // Then load the object from the database.
  // Finally call the populateObjectChildren method to      
  public void initialiseObjectsForCloning()
  {

    // Here we generate a keyprefixmap using the global describe 
    // Then compare that to our object to determine type.  
    Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); 
       
    Map<String,String> keyPrefixMap = new Map<String,String>{};
          
    for(String sObj : gd.keySet()){
      Schema.DescribeSObjectResult r =  gd.get(sObj).getDescribe();
      keyPrefixMap.put(r.getKeyPrefix(), r.getName());
    }
      
    String objectID                 = ApexPages.currentPage().getParameters().get('id');
    String objectTypeKey            = objectId.subString(0,3);
      
    objectTypeName                  = keyPrefixMap.get(objectTypeKey);
      
    String primaryObjectQueryString = 'SELECT Id, Name FROM '
                                    + objectTypeName
                                    + ' WHERE Id = \''
                                    + objectId
                                    + '\'';
    
    headSObject = Database.query(primaryObjectQueryString);
    objectName          = '' + headSObject.get('Name');
    populateObjectChildren();    
  }

  // Get all of the children of the current object that have a object type
  // contained in the child object types page parameter.
  // Not restricting the child objects to particular types results in many
  // unclonable system objects being added to the possibiilites, which we need to avoid.
  // Making these object type choices also allows us 
  // pick and chose the specific kinds of objects we want to allow users to clone.  
  public void populateObjectChildren()
  {
       
    objectChildren = new List<relatedObjects>{};
        
    Set<String> childObjectTypes = new Set<String>{};
    
    // read the object types from the page parameter.    
    childObjectTypes.addAll(
         ApexPages.currentPage().getParameters().get('childobjecttypes').split(',')
    );
    
    // Use the sobjecttype describe method to retrieve all 
    // child relationships for the object to be cloned.    
    Schema.DescribeSObjectResult headDescribe = 
           headsObject.getSObjectType().getDescribe();
    
    List<Schema.ChildRelationship> childRelationships = 
           headDescribe.getChildRelationships(); 
    
    // Iterate through each relationship, and retrieve the related objects.       
    for (Schema.ChildRelationship childRelationship : childRelationships)
    {
      Schema.SObjectType childObjectType = childRelationship.getChildSObject();
      
      // Only retrieve the objects if their type is one of those we are interested in.          
      if (childObjectTypes.contains(childObjectType.getDescribe().getName()))
      {
        List<relatedObjectRow> relatedObjects = new List<relatedObjectRow>{};
                
        Schema.SObjectField childObjectField = childRelationship.getField();
                
        String relatedChildSObjectsquery = 'SELECT ID FROM ' 
                                         + childObjectType.getDescribe().getName()
                                         + ' WHERE '
                                         + childObjectField.getDescribe().getName()
                                         + ' = \'' 
                                         + headsObject.Id
                                         + '\''; 
                                                        
        for (SObject childObject : Database.query(relatedChildSObjectsquery))
        {
          relatedObjects.add(new relatedObjectRow(childObject));
        }
            
        if (!relatedObjects.isEmpty())
        {
          objectChildren.add(new relatedObjects(relatedObjects, 
                             childObjectType.getDescribe().getLabelPlural(), 
                             childObjectField.getDescribe().getName()));
        }  
      }
    }
  }
  
  // Perform the cloning process.
  // First clone the parent, then all of the child objects. 
  // Then redirect the user to the new object page.
  public PageReference doClone()
  {
   try
   {
      headClone = cloneObjects(new List<sObject>{headSObject})[0];
      insert headClone;
    
      cloneSelectedObjects();
    
      return new PageReference('/' + headClone.Id);
    }
    // If there is any kind of exception, then put a page message on the screen to inform the user.
    catch (exception e)
    {
      ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, e.getMessage()));
      return null;
    }
  }
  
  // Clone the selected child objects.
  // Associate the cloned objects with the new cloned parent object.
  public void cloneSelectedObjects()
  {
        
    List<sObject> clonedObjects = new List<sObject>{};
    List<sObject> selectedRelatedObjects;
     
    for (relatedObjects relatedObject : objectChildren)
    {
      selectedRelatedObjects = new List<sObject>{};  
      clonedObjects = new List<sObject>{};  
      
      for (relatedObjectRow row : relatedObject.objectRows) 
      {
        if (row.selected)
        {
          selectedRelatedObjects.add(row.obj);
        }
      }
      
      if (!selectedRelatedObjects.isEmpty())
      {
        clonedObjects = cloneObjects(selectedRelatedObjects);
        
        for (sObject clone : clonedObjects)
        {
          clone.put(relatedObject.relatedFieldName, headClone.Id);  
        }
        
        insert clonedObjects;
      }
    }
  }

  // Clone a list of objects to a particular object type
  // Parameters 
  // - List<sObject> sObjects - the list of objects to be cloned 
  // The sObjects you pass in must include the ID field, 
  // and the object must exist already in the database, 
  // otherwise the method will not work.
  public static List<sObject> cloneObjects(List<sObject> sObjects){
                                                
    Schema.SObjectType objectType = sObjects.get(0).getSObjectType();
    
    // A list of IDs representing the objects to clone
    List<Id> sObjectIds = new List<Id>{};
    // A list of fields for the sObject being cloned
    List<String> sObjectFields = new List<String>{};
    // A list of new cloned sObjects
    List<sObject> clonedSObjects = new List<sObject>{};
    
    // Get all the fields from the selected object type using 
    // the get describe method on the object type.    
    if(objectType != null)
    {
      for (Schema.SObjectField objField : 
           objectType.getDescribe().fields.getMap().values())
      { 
        Schema.DescribeFieldResult fieldDesc = objField.getDescribe();
        // If the field type is location, then do not include it,
        // otherwise it will cause a soql exception.
        // Note that excluding the field does not stop the location from
        // being copied to the new cloned object.
        if(fieldDesc.getType() != DisplayType.LOCATION)
        {
          sObjectFields.add(fieldDesc.Name);
        }
      }
    }
    
    // If there are no objects sent into the method, 
    // then return an empty list
    if (sObjects != null || 
        sObjects.isEmpty() || 
        sObjectFields.isEmpty()){
    
      // Strip down the objects to just a list of Ids.
      for (sObject objectInstance: sObjects){
        sObjectIds.add(objectInstance.Id);
      }

      /* Using the list of sObject IDs and the object type, 
         we can construct a string based SOQL query 
         to retrieve the field values of all the objects.*/
    
      String allSObjectFieldsQuery = 'SELECT ' + String.join(sObjectFields,','); 
    
      allSObjectFieldsQuery += ' FROM ' + 
                               objectType.getDescribe().getName() + 
                               ' WHERE ID IN (\'' + sObjectIds.get(0) + 
                               '\'';

      for (Integer i=1 ; i < sObjectIds.size() ; i++){
        allSObjectFieldsQuery += ', \'' + sObjectIds.get(i) + '\'';
      }
    
      allSObjectFieldsQuery += ')';
    
      // Execute the query. For every result returned, 
      // use the clone method on the generic sObject 
      // and add to the collection of cloned objects
      for (SObject sObjectFromDatabase:
           Database.query(allSObjectFieldsQuery))
      {
        clonedSObjects.add(sObjectFromDatabase.clone(false,true));  
      }

    }
   
    return clonedSObjects;
    
  }
  
  // Related objects data construct - used to store a collection of child objects
  // connected to the head object through the same relationship field.
  public class relatedObjects
  {
    public List<relatedObjectRow> objectRows       { get; set; }
    public String                 pluralLabel      { get; set; }
    public String                 relatedFieldName { get; set; }
    
    public relatedObjects(List<relatedObjectRow> objectRows, 
                          String pluralLabel, 
                          String relatedFieldName) 
    {
      this.objectRows       = objectRows;
      this.pluralLabel      = pluralLabel;
      this.relatedFieldName = relatedFieldName;
    }   
  }     

  // An indidual child object row. It simple contains the object definition, 
  // and a checkbox to select the row for cloning on the clone plus page.
  public class relatedObjectRow
  {
    public sObject obj      { get; set; }
    public Boolean selected { get; set; }
    
    public relatedObjectRow(Sobject obj)
    {
      this.obj      = obj;
      // All object rows are selected by default.
      this.selected     = true;
    }
    
    public String getId(){
      try{
        return '' + obj.get('Id');
      } catch (Exception e){
        return '';
      }    
    }
  }
}


Latest Version of Clone Plus Page Code:
<apex:page controller="ClonePlusController" action="{!initialiseObjectsForCloning}">
    
  <apex:sectionHeader title="Clone Plus: {!objectName}"/>

  <apex:pageMessages />

  <apex:form id="theform" >
  
    Please select the child objects you would like to clone.<br/><br/>
  
    <apex:repeat value="{!objectChildren}" var="child">
      <apex:PageBlock title="{!child.pluralLabel}"> 
        <apex:pageBlockTable value="{!child.objectRows}" var="objectRow">
          <apex:column headerValue="Clone" width="10%">
            <apex:inputCheckbox value="{!objectRow.selected}"/>
          </apex:column>
       <apex:column headerValue="Id"   value="{!objectRow.Id}" width="45%"/>
        </apex:pageBlockTable>
      </apex:PageBlock>
    </apex:repeat>
    
    <apex:PageBlock >
      <apex:commandButton action="{!doClone}" value="Clone"/>
    </apex:PageBlock>
  </apex:form>  

</apex:page>


Sample Test Class:
/*
    A test method for the clone plus controller.
*/

@isTest
public class ClonePlusControllerTest
{
    /*
        Test the initialise objects for cloning method.
        
        Initialise an instance of the clone plus controller, and add some test parameters to the test URL.
        
        In this test, we will add an account Id as the the object to be cloned.
    */
    private static testmethod void testInitialiseObjectsForCloning()
    {
        // First lets create our sample Account object and some related Contacts.
        Account acc = new Account (Name = 'Test Account', Phone = '01482 123456789'); 
        insert acc;
        
        List<Contact> cons = new List<Contact>{};
        cons.add(new Contact(AccountId = acc.Id, FirstName = 'Sam', 
                             LastName = 'Ple' , email='test1@salesforce.com'));
        cons.add(new Contact(AccountId = acc.Id, FirstName = 'Ted', 
                             LastName = 'Test', email='test2@salesforce.com'));
        insert cons;
        
        List<Opportunity> opps = new List<Opportunity>{};
        opps.add(new Opportunity(AccountId = acc.Id, Name = 'Test Opportunity 1', 
                                 StageName='Closed Won' , CloseDate=Date.TODAY()));
        opps.add(new Opportunity(AccountId = acc.Id, Name = 'Test Opportunity 2', 
                                 StageName='Closed Lost', CloseDate=Date.TODAY()));
        insert opps;
        
        // Put the newly created account Id as the Id parameter on the page.
        ApexPages.currentPage().getParameters().put('Id', acc.id);
        
        // Also, add the contact object as a child object to clone.
        ApexPages.currentPage().getParameters().put('childobjecttypes','Contact,Opportunity');
        
        // Initialise a test instance of a clone plus controller.
        ClonePlusController cpc = new ClonePlusController();
        
        // Now call the initialise method.
        cpc.initialiseObjectsForCloning();
        
        // Now verify that all the class variables have been correctly set.
        system.assertEquals(2              , cpc.objectChildren.size());
        system.assertEquals('Contacts'     , cpc.objectChildren[0].pluralLabel);
        system.assertEquals('AccountId'    , cpc.objectChildren[0].relatedFieldName);
        system.assertEquals('Opportunities', cpc.objectChildren[1].pluralLabel);
        system.assertEquals('AccountId'    , cpc.objectChildren[1].relatedFieldName);
        
        // The related contacts should all be loaded into the controller.
        system.assertEquals(2              , cpc.objectChildren[0].objectRows.size());
        system.assertEquals(cons[0].Id     , cpc.objectChildren[0].objectRows[0].getId());
        system.assertEquals(cons[1].Id     , cpc.objectChildren[0].objectRows[1].getId());
        
        // The related opportunites should all be loaded into the controller.
        system.assertEquals(2              , cpc.objectChildren[1].objectRows.size());
        system.assertEquals(opps[0].Id     , cpc.objectChildren[1].objectRows[0].getId());
        system.assertEquals(opps[1].Id     , cpc.objectChildren[1].objectRows[1].getId());
        
        // All related objects should be selected for clonign by default.
        system.assert(cpc.objectChildren[0].objectRows[0].selected);
        system.assert(cpc.objectChildren[0].objectRows[1].selected);
        system.assert(cpc.objectChildren[1].objectRows[0].selected);
        system.assert(cpc.objectChildren[1].objectRows[1].selected);        

  // These variables all store information about the head object (Account)
        system.assertEquals('Account'   , cpc.objectTypeName);
        system.assertEquals(acc.Name    , cpc.objectName);
        system.assertEquals(acc.Id      , cpc.headSObject.Id);
        system.assertEquals(null        , cpc.headClone);
    }
    
    /*
     Test the "doClone" method. This method clones the parent object held in the controller,
     and all the related objects that have also been selected for copying.

        Initialise an instance of the clone plus controller, and populate the class variables with
        some sample objects. Select some of the related objects for cloning and ignore some others
  
        After the method has run, query the database to make sure that only those objects selected
        for cloning have been replicated.
    */
    private static testmethod void testDoCloneSuccess()
    {
        // First lets create our sample account object and some related Accounts.
        Account acc = new Account (Name = 'Test Account', Phone = '01482 123456789'); 
        insert acc;
        
        List<Contact> cons = new List<Contact>{};
        cons.add(new Contact(AccountId = acc.Id, FirstName = 'Sam', 
                             LastName = 'Ple', email='test1@salesforce.com'));
        cons.add(new Contact(AccountId = acc.Id, FirstName = 'Ted', 
                             LastName = 'Test', email='test2@salesforce.com'));
        insert cons;
        
        List<Opportunity> opps = new List<Opportunity>{};
        opps.add(new Opportunity(AccountId = acc.Id, Name = 'Test Opportunity 1', 
                                 StageName='Closed Won' , CloseDate=Date.TODAY()));
        opps.add(new Opportunity(AccountId = acc.Id, Name = 'Test Opportunity 2', 
                                 StageName='Closed Lost', CloseDate=Date.TODAY()));
        insert opps;        
        
        // Initialise a test instance of a clone plus controller.
        ClonePlusController cpc = new ClonePlusController();
        
        // Populate the controller variables as if the Account created above had been 
        // selected for cloning along with all its related contacts.
        cpc.headSobject    = acc;
        
        List<ClonePlusController.RelatedObjectRow> relatedContactRows 
                               = new List<ClonePlusController.RelatedObjectRow>{};
        relatedContactRows.add(new ClonePlusController.RelatedObjectRow(cons[0]));
        relatedContactRows.add(new ClonePlusController.RelatedObjectRow(cons[1])); 
        
        List<ClonePlusController.RelatedObjectRow> relatedOpportunityRows 
                               = new List<ClonePlusController.RelatedObjectRow>{};
        relatedOpportunityRows.add(new ClonePlusController.RelatedObjectRow(opps[0]));
        relatedOpportunityRows.add(new ClonePlusController.RelatedObjectRow(opps[1]));
        
        // Deselect one of the Opportunities, indicating that it should not be cloned.       
        relatedOpportunityRows[0].selected = false;
        
        cpc.objectChildren = new List<ClonePlusController.RelatedObjects>{};
        cpc.objectChildren.add(new ClonePlusController.RelatedObjects(relatedContactRows,    
                                                                      'Contacts', 'AccountId'));
        cpc.objectChildren.add(new ClonePlusController.RelatedObjects(relatedOpportunityRows,
                                                                      'Opportunity', 'AccountId'));
        
        // Call the do clone method. Record the page reference passed in response. 
        // We will use this later in the test to ensure that the user is correctly being 
        // directed to the new cloned object detail page.
        PageReference doCloneResult = cpc.doClone();
        
        // Check the database for the presence of the newly cloned records.
        List<Account> clonedAccs = [SELECT Name, Phone,
                                          (SELECT FirstName, LastName, Email
                                           FROM Contacts
                                           ORDER By FirstName),
                                           (SELECT Name, StageName, CloseDate
                                           FROM Opportunities
                                           ORDER By Name)
                                    FROM Account
                                    WHERE Id != :acc.Id];
        
        system.assertEquals(1                , clonedAccs.size());
        system.assertEquals(acc.Name         , clonedAccs[0].Name);
        system.assertEquals(acc.Phone        , clonedAccs[0].Phone);
        
        //Both of the selected contacts should have been cloned.
        system.assertEquals(2                , clonedAccs[0].Contacts.Size());
        system.assertEquals(cons[0].FirstName, clonedAccs[0].Contacts[0].FirstName);
        system.assertEquals(cons[0].LastName , clonedAccs[0].Contacts[0].LastName);
        system.assertEquals(cons[0].email    , clonedAccs[0].Contacts[0].Email);
        system.assertEquals(cons[1].FirstName, clonedAccs[0].Contacts[1].FirstName);
        system.assertEquals(cons[1].LastName , clonedAccs[0].Contacts[1].LastName);
        system.assertEquals(cons[1].email    , clonedAccs[0].Contacts[1].Email);
        
        // Only 1 opportunity should have been cloned.
        system.assertEquals(1                , clonedAccs[0].Opportunities.Size());
        system.assertEquals(opps[1].Name     , clonedAccs[0].Opportunities[0].Name);
        system.assertEquals(opps[1].StageName, clonedAccs[0].Opportunities[0].StageName);
        system.assertEquals(opps[1].CloseDate, clonedAccs[0].Opportunities[0].CloseDate);
        
        // Finally, verify that the page reference returned by the clone method is the 
        // view page of the new account object.
        system.assertEquals('/' + clonedAccs[0].Id, doCloneResult.getURL());
    }

    /*
        Test the "doClone" method. This method clones the parent object held in the controller,
        and all the related objects that have also been selected for copying.

        Initialise an instance of the clone plus controller, and don't populate the class variables with
        some sample objects. This will cause an error when calling the do clone method, 
        as there is nothing to clone. 
  
        Instead of redirecting the user, a page message should be added to the current page, 
        informing the user of the error that has occured.
    */
    private static testmethod void testDoCloneFailure()
    {   
        // Initialise a test instance of a clone plus controller.
        ClonePlusController cpc = new ClonePlusController();
        
        // Call the do clone method. Record the page reference passed in response. 
        PageReference doCloneResult = cpc.doClone();
        
        // Verify that that a null page reference is returned by the method call, 
        // and that a page message has been generated to inform the user a fault has occured.
        system.assertEquals(1, ApexPages.getMessages().size());
    }
    
    /*
     When the related object rows have been populated, the getId method can be used to interrogate
     the generic sobject and retrieve the id field of the object.
      
     However, if no id field is visible to the user, then this should not cause an exception to occur.
     Instead, a blank value should be returned.
    */
    private static testmethod void testGetObjectIdException()
    {
     // Start the test by intialising some object rows in the controller. 
     // One object row will not contain an object, resulting in a (caught) exception 
     // when the id is requested.
     Contact testSavedContact = new Contact(FirstName = 'Sam', LastName = 'Ple', 
                                            email='test1@salesforce.com');
     insert testSavedContact;
     
     ClonePlusController.RelatedObjectRow testSavedContactRow 
                         = new ClonePlusController.RelatedObjectRow(testSavedContact);
     ClonePlusController.RelatedObjectRow testNullObjectRow   
                         = new ClonePlusController.RelatedObjectRow(null);
     
     System.assertEquals(testSavedContact.Id, testSavedContactRow.getId());
     System.assertEquals(''                 , testNullObjectRow.getId());
    }
}


My advice to all those using the test class (especially who have previously asked about testing), is that you should use this as a base for building your own test methods. Substitute the test Account -> Contact and Account -> Opportunities relationships used in the test for the objects you are going to clone in your own org.

One last thought, although the minimum coverage is 75%, I have built the test class so that the coverage reaches 100%. As a general rule of thumb I always intend to write complete coverage of code, as the more coverage you have, the more likely you are to catch the introduction of a bug in your system after some new development, or a new release.