0

Here is the handler which contains meta data Petrol_Price__mdt due to which my record in test class is not getting inserted.

public with sharing class HandlerExpenseManagement{

    public void fillDateValues(List<ExpenseManagement__c> currentListOfExpenses){
        system.debug('hiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii1');
         String ownerName = [select name from User where id =: currentListOfExpenses[0].ownerId LIMIT 1].name; 
         Petrol_Price__mdt  custm = new Petrol_Price__mdt();
                custm = [SELECT Month__c, Price__c, Year__c  FROM Petrol_Price__mdt where 
                                         Month__c =: currentListOfExpenses[0].MonthText__c and Year__c =: currentListOfExpenses[0].Year__c ];
        for(ExpenseManagement__c expense: currentListOfExpenses){     
            Date dToday = System.Today();

            if(expense.OnlyDate__c != null){  
                Datetime dt = datetime.newInstance(expense.OnlyDate__c.year(), expense.OnlyDate__c.month(),expense.OnlyDate__c.day());                        
                expense.Date__c = dt;
            }
            else if(expense.Date__c != null)
                expense.OnlyDate__c = expense.Date__c.date();

            if(expense.SubmittedForApproval__c == false) 
                expense.name = ownerName + '-' + expense.Date__c.format('dd-MM-yyyy') + ' -(' + expense.Location__c + ')';

           if(expense.TestMMIDistance__c != null){
           if(expense.location__c == 'Local')
             expense.localsystemCalculated__c = (expense.TestMMIDistance__c * custm.Price__c)/1000;
             else if(expense.location__c == 'Outstation'){
             system.debug('entered outstation calcuation------');
             expense.TransportExpense__c = (expense.TestMMIDistance__c * custm.Price__c)/1000;
             }
                system.debug('local amount'+expense.TransportExpense__c);
                    system.debug('local amount'+expense.localsystemCalculated__c);
            }

        }

    }

and here is my test class -

@Istest
public class TestHandlerExpenseManagement {
     public static testMethod void addAttchmentsExpense()
    {
        Profile standardProf = [select id from profile where name='Standard User'];
    User su = new User(
        alias = 'standt',         
        email='standarduser@azugatask.com',
        emailencodingkey='UTF-8',
        FirstName='task',
        lastname='testing',
        languagelocalekey='en_US',
        Region__c ='India',
        localesidkey='en_US',
        profileid = standardProf.Id,
        timezonesidkey='America/Los_Angeles',username='standarduser@azugatask.com');
    System.runAs(su) 
    {
        List<ExpenseManagement__c> explist = new List<ExpenseManagement__c>();
        ExpenseManagement__c exp = new ExpenseManagement__c();
        exp.Name='TestExpense';
        exp.Date__c=date.parse('6/28/2018');
        exp.DummyStage__c = 'start trip';
        exp.OwnerId=su.Id;
        explist.add(exp);
        try
        {
        insert explist;
        System.debug('this is expense'+explist);
        }
        catch(Exception e)
        {
            System.debug('insertion of expense failed'+e.getMessage());
        }
        /*Petrol_Price__mdt  custm = new Petrol_Price__mdt();
                custm = [SELECT Month__c, Price__c, Year__c  FROM Petrol_Price__mdt where 
                                         Month__c =: explist[0].MonthText__c and Year__c =: explist[0].Year__c];*/
        Test.startTest();
        HandlerExpenseManagement hem = new HandlerExpenseManagement();
        Map<Id,ExpenseManagement__c> oldmap = new Map<Id,ExpenseManagement__c>(); 
        hem.fillDateValues(explist);
        HandlerExpenseManagement.approveallexpenses(explist, oldmap); 
        Test.stopTest();
    }
    }    
}

Please help me get the record inserted or help me with the reason why it isnt getting inserted!!

  • What exactly is the issue? Are you trying to insert a Custom Metadata record in test context? – David Reed Jul 1 '18 at 12:59
  • No i m trying to insert Expense management record in test class which is failing stating that List has no rows for assignment in in the Metadata query which is there in the handler – Ashutosh Srivastava Jul 1 '18 at 13:00
  • 1
    I wrote an answer before I realized that this was a duplicate of your question from two days ago. Please do not create duplicate questions. Instead, edit existing ones to add information where needed. – David Reed Jul 1 '18 at 13:17
2

The proximate issue here is that your code is vulnerable to a QueryException. You clearly need a failure case for

            custm = [SELECT Month__c, Price__c, Year__c  FROM Petrol_Price__mdt where 
                                     Month__c =: currentListOfExpenses[0].MonthText__c and Year__c =: currentListOfExpenses[0].Year__c ];

since that code will throw an exception if there are no responsive records. That is the case here.

Custom Metadata records are, as the name implies, metadata. This means that real Custom Metadata records in your org can be seen by your unit tests, without using the SeeAllData=true annotation. Unfortunately, it also means that you cannot insert or update Custom Metadata records in a test context like you could with Custom Settings, to ensure that a query like the one above completes successfully.

The below does not work, you get an AsyncException.

@isTest
public class TestCustomMetadataInsertion {
    @isTest
    public static void insertCustomMetadata() {
        Metadata.CustomMetadata customMetadata =  new Metadata.CustomMetadata();
        customMetadata.fullName = 'Visitor_Resource__mdt.Test';
        Metadata.CustomMetadataValue customField = new Metadata.CustomMetadataValue();
        customField.field = 'Amount__c';
        customField.value = 1;
        customMetadata.values.add(customField);

        Metadata.DeployContainer container = new Metadata.DeployContainer();
        container.addMetadata(customMetadata);
        Id asyncResultId = Metadata.Operations.enqueueDeployment(container, null);
    }
}

Hence, if you're testing code that depends upon the presence of Custom Metadata records, you must do one of two things:

  1. Ensure that you have real Custom Metadata records in place in your org. This would apply if those records are really part of your org's configuration (which is the intended use case of Custom Metadata) and rarely change, meaning that your unit tests don't become fragile. In most single-org use cases (i.e., not a package to be deployed to other orgs), this is feasible. However, since you're testing date-based code with Custom Metadata, it's not clear that this approach would work for you.
  2. Use an inversion of control pattern to allow your test code to directly set a Custom Metadata variable in your class that's being tested. You can construct Custom Metadata values in memory in your unit test by using JSON deserialization, or you can implement a pattern where your class actually stores the values derived from Custom Metadata rather than the metadata itself, and allow your test code to set those values directly to avoid making the query, or you can use Test.isRunningTest() to dynamically determine whether or not to make the query and supply defaults.

These approaches are covered beautifully in the answers on this StackExchange question, for more specific details.

| improve this answer | |
  • ?? Javascript deserialization ?? -- didn't you mean Json deserialization? – cropredy Jul 1 '18 at 23:01
  • Sure did. Should've woken up before I started typing. Thanks for the catch. – David Reed Jul 1 '18 at 23:04
1

Mocking custom metadata for tests (an alternate approach)

  • Use a Selector layer for your SOQL as defined in Trailhead. This would look be invoked like: Petrol_Price__mdt[] petrolPrices = new PetrolPrices().selectByMonthAndYear(aMonth,aYear);

  • Mock results for this selector using either the Json deserialize technique or the SObjectFabricator GitHub lib (recommended). You do this because custom metadata sobjects can't be explicitly constructed.

For example, using sfab:

Petrol_Price__mdt[] mockPrices = new List<Petrol_Price__mdt> {
   (Petrol_Price__mdt) new sfab_FabricatedSObject(Petrol_Price__mdt.class)
    .setField(Petrol_Price__mdt.Price__c,50.00)
    ...
    .toSObject()

  }

and (caveat - mental energy required to learn this if not already familiar) ..

  • ApexMocks (or equivalent) to inject a mock selector class object into the runtime so you can return your mocked Petrol Prices from above. ApexMocks exploits the StubApi but provides a useful framework (based on Mockito) so you don't have to do so much work. Read the links below to get more details.

The big win for ApexMocks is you don't have to put any Test.isRunningTest() code in your production classes and you can truly isolate your tests from the production values in the custom metadata types. As you get more familiar with it, and especially with the other patterns: Unit of Work, Domain, and Service layers; you can accomplish a lot of unit testing without having to do any DML setup - more comprehensive testing and faster tests too!

ApexMocks recommended reading

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.