1

I have a method to get the NavigationMenuItems for a provided Community is is possible to test this without using @TestVisible? So far I have tried to insert a community and NavigationMenu and NavigationMenuItem but I get an exception that DML is not allowed.

Here is the method I am testing:

public static List<NavigationMenuItem> getNavigationMenuItems(String menuName, String communityId) {
    List<NavigationMenuItem> navigationMenuItems = [SELECT Label,Type, Target, TargetPrefs, DefaultListViewId, Position
                                                    FROM NavigationMenuItem
                                                    WHERE NavigationLinkSet.DeveloperName = :menuName 
                                                          AND Status = 'Live'
                                                          AND NavigationLinkSet.NetworkId = :communityId
                                                    ORDER BY Position];

    return navigationMenuItems;
  }

2 Answers 2

4

If you asking whether or not you can avoid having to insert NavigationMenuItem instances into the database, then the answer is yes, though ideally you would move away from simply using static methods and start using instance methods and separation of concerns so you can start mocking classes.

The first option is to take the quick but dirty approach, though this means adding testing-specific code to your production code (and personally I wouldn't use it), something like:

public static List<NavigationMenuItem> getNavigationMenuItems(String menuName, String communityId) {
    List<NavigationMenuItem> navigationMenuItems;

    if (!Test.isRunningTest()) {
        navigationMenuItems =
            [SELECT Label,Type, Target, TargetPrefs, DefaultListViewId, Position
                FROM NavigationMenuItem
                WHERE NavigationLinkSet.DeveloperName = :menuName 
                    AND Status = 'Live'
                    AND NavigationLinkSet.NetworkId = :communityId
                ORDER BY Position];
    } else {
        String itemsAsJSON = '[{"Label": "Abc", "Type": "ExternalLink", "Status": "Live", "Target": "www.abc.com"}]'; // Your test data here

        navigationMenuItems = JSON.deserialize(itemsAsJSON, navigationMenuItems);
    }

    return navigationMenuItems;
}

This uses hard-coded JSON to allow in-test creation of NavigationMenuItem instances as you need, but cannot vary what is returned for different tests (that's something you could mess around with but you will start including more and more stuff in production code just for testing purposes).

The "clean" alternative I would use is to split the querying into a separate class, using a pattern similar to those used by the fflib, the selector, and start using instance (not static) methods. This then allows you to separately mock out the selector using either the StubProvider or a specialized implementation of the selector for testing purposes (this requires the class to be virtual and to have virtual methods which the stub provider does not). This mock can use the same approach of JSON deserialization to instantiate fake versions of these items.

You do, however, need to ensure you apply some form of dependency injection pattern to allow the implementation of the selector to be varied between production execution and test execution. There are different dependency injection patterns you can use (e.g. constructor or factory method/singleton) that you'll find documented in various places around the web. There's even the fflib apex mocking framework that you could adopt which recommends certain dependency injection approaches.

This "clean" approach requires significant refactoring to your over-all code, to move from static methods to instance methods and to split the code into different "layers", but it really is worth it.

1
  • Thanks for your response. This is just one method that is use by an custom Navigation LWC for a community. I will definitely take a look at mocking for future use cases.
    – Jordan
    Commented Mar 23, 2021 at 17:27
2

@TestVisible is used to make private methods visible to tests so is not relevant here.

Some objects are not intended to be modified at runtime, but finding a comprehensive list of those is tricky. One list is Setup Objects.

If the goal is to test a query method, the mocking approach has the problem that the mocking code is replacing the query. So great for testing other code that depends on the query, but does not confirm that the query is valid. And queries can effectively implement many business logic needs, including complex and important ones.

A pragmatic approach here is to just call this method from your test passing in values that will never match and assert that you get back no rows.

(A bigger effort would be to base your tests on an assumed already established configuration. The tooling to automatically setup such environments has got better, but AFAIK it is still not possible to do that fully for a Community/Experience.)

4
  • I don't disagree, though unit testing a query is generally less important than testing the business logic that then operates on the results. Which is why I believe the mocking approach is acceptable. You can always test a query in an ad-hoc way via Anonymous Apex (easier to retain binding variables) or SOQL execution in your IDE/Developer Console.
    – Phil W
    Commented Mar 23, 2021 at 12:57
  • Wanted to make the point that if the unit test is testing the mock then that test is pointless. I've seen people actually do that and then have their work fail in QA because of a logical problem in the query.
    – Keith C
    Commented Mar 23, 2021 at 13:15
  • Thanks for your response. I made a mistake in my question it was isTest(SeeAllData=true) that I was thinking of not @isTestVisible. I originally took the approach of calling the method and asserting that the response had no rows as you suggested but wanted to see if there was a better way to handle it. This is just one method used by a LWC to to create a custom navigation for community.
    – Jordan
    Commented Mar 23, 2021 at 17:22
  • If you have code that depends on the responses then also mock as Phil W suggests. Though instead of using Test.isRunningTest() you can have your own @TestVisible switch to mock by default for other classes and run the actual query returning no rows to test this method.
    – Keith C
    Commented Mar 23, 2021 at 18:06

You must log in to answer this question.

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