1

I am trying to test some code that is working in the sandbox. The code, shown below, uses a reference to obtain the BusinessHoursId field of the Milestone's Case object (m.Case__r.BusinessHoursId):

public static Integer getNumMinutesTaken(Milestone__c m) {
    Long diffInMillis = BusinessHours.diff(m.Case__r.BusinessHoursId, m.Start__c, m.CompletionDatetime__c);
    return (Integer)diffInMillis / (1000 * 60);
}

This works fine in the sandbox, as I have said, but for some reason I cannot use the same type of reference in my test code to obtain the BusinesshoursId:

// Test Code Snippet
Case c = getValidCase();
c.BusinessHoursId = defaultBH.Id;
insert c;
System.debug('c.Id'+c.Id); // valid id
System.debug('c.BusinessHoursId'+c.BusinessHoursId); // valid id

Datetime start = datetime.newInstance(2012, 10, 5, 16, 0, 0);
Datetime completed = datetime.newInstance(2012, 10, 5, 17, 0, 0);

Milestone__c ms = new Milestone__c(Case__c = c.Id, Start__c = start, CompletionDatetime__c = completed);

System.debug(ms.Case__r.BusinessHoursId); // null
Integer timeTaken = MilestoneUtils.getNumMinutesTaken(ms);

System.assertEquals(60, timeTaken);

As far as I can see, this is the same reference as in my method, but it is returning null. Is there a difference to the allowable ways of referencing things in test code as compared to live code?

3 Answers 3

4

I would expect this to always return null, regardless of context. The fact that it is working some of the time is a surprise to me.

Generally, unless you have explicitly queried for fields on a related record, a reference using the relationship name will return null or throw a 'field not queried' exception. In your case, if you want to refer to the field ms.Case__r.BusinessHoursId, you would need to insert the ms record, then query for that same record:

insert ms;
ms = [select Id, Case__r.BusinessHoursId from Milestone__c where Id = :ms.Id];
System.debug(ms.Case__r.BusinessHoursId); //should give you the right value

Added as explanation:

The lookup field itself (ms.Case__c) can be thought of as simply a text/Id field with a primitive String value in it. The relationship name, ms.Case__r actually refers to an entire object that is sort of stuck onto the object you queried (ms) as an object attribute. You can use additional relationships to retrieve more related objects, such as ms.Case__r.Account.Name, in which you can think of ms.Case__r.Account as being an entire object based on the field ms.Case__r.AccountId.

In a trigger, all fields on the triggering object are available, but no fields on related objects are available, so one of the most common Trigger patterns involves collecting related record Ids and querying for them separately from the triggering records. In most other contexts, only the fields you query for will be in the object record you're working with. In the case of your getNumMinutesTaken method, you are assuming that the milestone record you're passed was queried on fields Case__r.BusinessHoursId, Start__c and CompletionDatetime__c. You're not querying for or referring to Case__c, since you don't need to know the actual Id of the Case, just some fields on the Case object.

2
  • Thanks for your answer. You are right, I had explicitly queried for the field in my live code. You answered a related reference query for me before, but I still don't understand exactly when references can be used to obtain further values from a related object, and when we must query for the fields on the related object. In my getNumMinutesTaken method, it seems to me that I am using a reference to get the field on a related object m.Case__r.BusinessHoursId, yet I can't just supply the Case__c Id because it seems the reference isn't actually used, but rather I need the actual field.
    – Joe
    Commented Oct 3, 2012 at 11:30
  • Many thanks for the time you took to update this. It has helped me to clear up my understanding of this issue. I think that, coming from Java, I am intuitively expecting all fields of an object to be in-memory, but actually the sObjects referenced in the code are constructed from data obtained from the database so you have to explicitly select every field you want to use.
    – Joe
    Commented Oct 5, 2012 at 8:01
2

Generally you do have to explicitly query for the related fields, though in test methods it is actually possible to fabricate relationships in memory without committing records to the database:

Account a = new Account(Name='MyAcc');
Opportunity o = new Opportunity(Name='MyOpp',Account=a);
OpportunityLineItem oli = new OpportunityLineItem(Opportunity=o);
system.debug(oli.Opportunity.Account.Name);

So here I have assigned the Account sobject to Opportunity.Account instead of assigning a.id to Opportunity.AccountId.

The same works for custom objects:

ObjectOne__c o1 = new ObjectOne__c(Name='MyObj1');
ObjectTwo__c o2 = new ObjectTwo__c(ObjectOne__r=o1);
ObjectThree__c o3 = new ObjectThree__c(ObjectTwo__r=o2);
system.debug(o3.ObjectTwo__r.ObjectOne__r.Name);

However, you cannot assign a list of sobjects to a detail relationship - for example adding a list of Opportunity Line Items to an Opportunity.

1
  • Using this method doesn't require that you insert the milestone record, you just specify in your test class that Case__r = c instead of Case__c = c.Id. Commented Oct 3, 2012 at 12:39
1

Can you please insert that milestone first of all and first check.

If still error the following conclusion :

Relationship reference dont work in Test classes i feel unless you explicitly query using __r relationship field .

Need to query once where id is inserted Id .

0

You must log in to answer this question.

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