Call Me Maybe - Using the Callable Interface to Build Versioned APIs

In Winter ‘19, Salesforce introduced the Callable Interface.

Enables developers to use a common interface to build loosely coupled integrations between Apex classes or triggers, even for code in separate packages. Agreeing upon a common interface enables developers from different companies or different departments to build upon one another’s solutions. Implement this interface to enable the broader community, which might have different solutions than the ones you had in mind, to extend your code’s functionality.

In short, you implement the interface and its single call method, pass it the name of the action you want to call and a Map<String,Object> with any necessary parameters, which dispatches the logic from there.

public class CallMeMaybe implements Callable {
    public Object call(String action, Map<String, Object> args) {
        switch on action {
            when 'doThisThing' {
                service.doThisThing();
            }

            when 'doThatThing' {
                service.doThatThing();
            }
        }
        return null;
    }
}
public class Caller {
    public void callTheCallable() {
        if (Type.forName('namespaced__CallMeMaybe') != null) {
            Callable extension = (Callable) Type.forName('namespaced__CallMeMaybe').newInstance();
            extension.call('doThisThing', new Map<String,Object>());
        }
    }
}

There’s nothing too novel here other than the conveniences this new standard interface gives us, the largest being the ability to execute methods in other packages without having a hard dependency on that package. What jumped out to me, however, was the idea of dispatching actions using a string parameter and how we can use that to build more flexible APIs in managed packages.

Versioned APIs

One way to expose a method for execution in a managed package is to mark it as global. These global methods serve as an API to your package. However, if you ever wanted to adjust the behavior of a global method, you risked causing unintended side affects on subscribers that depend on the original implementation. To get around this, I generally see packages create additional global methods with names like myMethodV2.

The finality of global methods tend me to make me agonize over creating them. Yes, you can deprecate them, but it felt like you were polluting your namespace. myMethodV2 may seem ok, but myMethodV16 starts to feel a little messy. Did you know there are 15 The Land Before Time movies? It’s not a good look.

Instead, what if you created a single Callable entry point into your org as an API?

public class VersionedAPI implements Callable {
    public Object call(String action, Map<String, Object> args) {
        //format actions using the template "domain/version/action"
        //e.g. "courses/v1/create"

        List<String> actionComponents = action.split('/');
        String domain = actionComponents[0];
        String version = actionComponents[1];
        String method = actionComponents[2];

        switch on domain {
            when 'courses' {
                return courseDomain(version, method, args);
            }

            when 'students' {
                return studentDomain(version, method, args);
            }

            ...
        }
        return null;
    }

    public Object courseDomain(String version, String method, Map<String, Object> args) {
        if (version == 'v1') {
            switch on method {
                when 'create' {
                    return courseServiceV1.create();
                }
                ...
            }
        } else if (version == 'v2') {
            switch on method {
                when 'create' {
                    return courseServiceV2.create();
                }
                ...
            }
        }
    }

    ...
}

By following this pattern, you’ll have a little more flexibility in defining your exposed methods without having to worry about the permanence of that method.

  • Typos in your action names aren’t forever anymore!
  • Remove actions that you don’t need. No more ghost town classes filled with @deprecated methods
  • Use new versions to change an actions behavior while allowing your subscribers to update their references at their convenience
  • Experiment with new API actions in a packaged context without fear of them living in the package forever if you change your mind

Of course, with this added flexibility comes the burden of communicating these changes out to your subscribers - if you remove an action, make sure to have a migration plan in place so your subscribers aren’t suddenly faced with a bug that you introduced. By following this pattern, however, I hope it will encourage more developers to expose more functionality as well as foster inter-package testing.

Apex Quirks - Interfaces

The best part about working with code that you didn’t write is learning something new, like running into a new pattern or a feature that you didn’t even know existed. For the most part, you’ll think to yourself “Neat!” put it in your pocket for future use, and move on. Occasionally, however, you’ll run into something that stops you in your tracks. I don’t just mean confusing, spaghetti code. I’m talking about the times when you think, “Wait, that shouldn’t even work.”

I stumbled upon a piece of code that implemented an interface, yet only contained static methods. For example:

public interface MyInterface {
    void doTheThing();
}
public with sharing class StaticImplementation implements MyInterface {
    public static void doTheThing() {
        System.debug('I\'m static!');
    }
}

To my understanding, an interface is fulfilled using instance methods, like so:

public with sharing class InstanceImplementation implements MyInterface {
    public void doTheThing() {
        System.debug('I\'m an instance!');
    }
}

MyInterface instanceImp = new InstanceImplementation();
instanceImp.doTheThing(); //Outputs "I'm an instance!"

But StaticImplementation was compiling successfully, so it appears to be valid. At first I thought, “Can you actually execute static methods off of an instance of that class?”. Let’s try:

StaticImplementation staticImp = new StaticImplementation();
staticImp.doTheThing(); //Error: Static method cannot be referenced from a non static context

As I expected, you cannot. So at least I wasn’t grossly mistaken on how static methods are called. But if the interface is being fulfilled by that class, what happens when I call that method using the interface declaration? I guessed we’d get a weird runtime error:

MyInterface staticImp = new StaticImplementation();
staticImp.doTheThing(); //Outputs "I'm static!"

WHAT?! The static method is being run as an instance method!

I’m 99% sure this is a bug in the Apex compiler that just happens to work. Otherwise I am very mistaken in my understanding of how interfaces work in object oriented languages.

So what does this mean? My immediate thought was “Maybe I can use this to mock static methods!” But a cautionary quote came to mind:

“Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.” - Ian Malcolm in Jurassic Park

My advice is don’t try to leverage this as a feature. At best, you’ll confuse other developers, including yourself in 6 months, as to why this works. At worst, if this is actually an Apex bug and is fixed in an update, you’ll find yourself locked into older Apex API versions until you refactor this “feature” out of your existing code.

As a developer, you should aim to write code that has clear intent and is human readable. Leveraging “hidden” features/bugs is a red flag because it will potentially confuse others down the road, will have little to no documentation, and may not even exist in the future. “Clever” code should not come at the expense of clean and clear code, especially in a scenario like this that gives you so little benefit.

Hopefully if you run into this problem, now you’ll understand why it works, and why you should not do the same.

Lightning Web Components - Testing

Happy New Year!

During the last post we explored some of the basics of developing with LWC. Now let’s see what options we have for testing.

I have to admit that I have never felt comfortable with front-end JavaScript testing on Salesforce. A few years ago I did a Dreamforce presentation on using Jasmine to test your Visualforce pages. The whole point was to lay the foundation of what was technically possible, but in practice it required so much setup and DOM mocking that it just never really caught on for my own day to day at work.

Once I started working with Aura, testing felt even more out of reach. How do I test each JavaScipt controller method? How would I mock out server calls? Did I have to make make a lightning application just for the purpose of running tests on components? With all these questions and doubts, I instead chose to ignore front-end testing all together.

To be clear, I am a huge proponent of test driven development and I generally dislike making any changes to code without tests to help me confirm that nothing else broke along the way. But when you are crunched for time, these things just sort of get push aside for later. Surely someone smarter than me will figure this out, right? This is one of the rare occassions where my procrastination actually paid off.

Enter lwc-jest

Jest is a JavaScript testing framework created by Facebook. lwc-jest is Salesforce’s extension of this framework to support testing Lightning Web Components. I have to commend the LWC team for continuing down this path of using existing, proven technologies, as well for making lwc-jest open source. This again has made it easy for me to find answers to my questions while troubleshooting my code.

On top of that, this testing framework works from the command line and runs entirely on your local machine. That’s right, Salesforce development all from the comfort of your computer. No WiFi? No problem! You can’t view your component in your browser locally (at least, not that I know of), but you can code and run your tests without even authenticating with a scratch org or sandbox.

For those of you playing along at home, here are some resources to help you follow along:

LWC Testing Documentation

GitHub repo with project code

Lesson 1: Machine setup is tricky on Windows

Salesforce’s documentation does a fantastic job on setting up Jest on your machine. That is, if you’re using a Mac, where everything worked fairly seemlessly. I tried this on a Windows machine and ran into some issues along the way. To be fair, I am not very experienced with Node.js so a lot of these problems could just be a result of my inexperience and just issues with running Node on a Windows machine.

I got to the step where I was running npm install in Command Prompt and receive this error:

gyp ERR! configure error
gyp ERR! stack Error: Can't find Python executable "python", you can set the PYTHON env variable

Ok, so clearly I need to install python. I installed python3 and set the env variable on my machine and tried again. That threw a python syntax error. I wish I saved the error, but in the end it turns out I needed to use python2 instead of python3. This wasn’t a problem on my Mac because python2 is installed by default.

I tried again and ran into another error along the lines of needing .NET Framework 2.0 SDK installed, which I could resolve by installing Visual Studio. I tried that and the error didn’t go away. This was a couple hours into this and while the issue was probably due to something I was or was not doing, I couldn’t help but wonder if the package itself just had Windows issues that were beyond me. Either way, I gave up and tried again using Windows Subsystem for Linux (WSL), which I already had set up on my machine.

Setup worked out fine once I switched to WSL, but there were still some issues. Sometimes tests would fail and then suddenly pass when run again. This didn’t seem to happen on my Mac. Also, the tests ran about 4 times faster on my Mac, though that could be because I’m running the tests in WSL instead of native Linux. The difference was about 5 seconds on a Mac compared to 20 seconds on Windows. This is still blazingly fast compared to some Apex tests, but it was still consistently noticeable.

I hit a few bumps along the way, but I’m happy that Jest was working and I could get to fun part of writing tests.

Lesson 2: What am I testing again?

Before I jumped in, I had to wrap my head around front-end testing. Apex testing comes fairly naturally to me now: set up your expected data, call the methods with your inputs, does this method do what I expect it to? Does this flow do what I expect it to? I don’t know why this never translated well for me when it came to front-end testing. An application could do so much, what was necessary for testing? Do I need to assert on pixels in the margins? That tags had the appropriate class names? This analysis paralysis usually led to inaction with me just settling with “I’ve clicked through the front-end, I’m sure it’s fine”.

Since LWC is component based, this was a little easier to digest. Components tend to do fairly discrete things and if they don’t maybe the component is doing too much. lwc-jest made isolating your tests even easier since it allowed you to render a component by itself, call its methods, and interact with its public properties.

With this modularization in mind, I came to the conclusion that I should test what I can access in the test, coming up with the following essentials:

  • Does the component display the tags and data you want to display?
  • Does the component respond to interactions as expected?
  • Does the component’s public methods and properties (i.e. those with the @api decorator) work as expected?

You can always test more later, but I consider these the MVP of your tests. In the tests for the inputTableCell component, I focus just on that:

  • Are the passed in @api decorated properties for name and record used to display the correct data properly?
  • Is the lightning-input element present?
  • Does the @api decorated method inputValue() return the correct data after you input data?
beforeEach(() => {
    element = createElement('c-input-table-cell', { is: InputTableCell });
    element.record = { 'Name' : 'Test Name'};
    element.field = 'Name';
    document.body.appendChild(element);

    lwcInput = element.shadowRoot.querySelector('lightning-input');
});

describe('Input table cell', () => {
    it('should have the correct label and name', () => {
        expect(lwcInput.label).toBe('Name');
        expect(lwcInput.name).toBe('Name');
    });
    
    it('displays an input field', () => {
        expect(lwcInput.value).toBe('Test Name');
    });
    
    it('returns inputted values', () => {
        const expectedName = 'Updated Name'
        lwcInput.value = expectedName;
        const typeEvent = new Event('change');
        lwcInput.dispatchEvent(typeEvent);

        const inputValue = element.inputValue();
        expect(inputValue.value).toBe(expectedName);
        expect(inputValue.field).toBe('Name');
    })
});

I really like this testing format. The describe methods create easy to read documentation as to what this component does without having to hijack method names like I do in Apex (e.g. @isTest private static void shouldReturnInputValueWhenChanged())

Lesson 3: Get good at query selectors and events

In Jest, you are working with a simulated DOM as if you were in the browser. In other words, the tests don’t actually render the page, at least, not in a way that you can look at and interact with it. You will need to write JavaScript query selectors in order to assert on whether or not they present. You’ll also have to fire off your own Events when you interact with your component. For example, let’s take the input from inputTableCell:

<lightning-input variant="label-hidden" label={field} name={field} value={value} onchange={handleInputChange}></lightning-input>

When the input is changed, handleInputChange() should be called, which in turn updates the value returned by the public method inputValue(). In my test, I initially tried the following:

it('returns inputted values', () => {
    const expectedName = 'Updated Name'
    lwcInput.value = expectedName;

    const inputValue = element.inputValue();
    expect(inputValue.value).toBe(expectedName);
})

This didn’t work because updating the value on the element doesn’t actually fire the change event. You need to do that yourself like so:

const typeEvent = new Event('change');
lwcInput.dispatchEvent(typeEvent);

Again, standards come to rescue as I was easily able to find this documented online.

However, whenever my tests failed I couldn’t help but wonder if I had just written an Event or a query selector incorrectly. At first I was just running my tests over and over again, but once I figured out how to use the interactive debugger, I was able to save myself some time.

Lesson 4: Use the debugger to interact with your code during execution

If you run your tests using the debug flag (i.e. lwc-jest --debug), tests will actually stop at breakpoints in your code so you can interact with the code in that context. For example, I placed a random debugger in my tests:

it('should have the correct label and name', () => {
    console.log('here');
    debugger;
    expect(lwcInput.label).toBe('Name');
    expect(lwcInput.name).toBe('Name');
});

To interact with the debugger, in Chrome go to chrome://inspect and click “Open dedicated DevTools for Node”

Launch Node Debugger

When you run your tests the debugger will freeze once, indicating tests are ready to run. Press the play button and the debugger will eventually freeze again at your breakpoint, where you can interact with the variables in that context. (This may take some time based on how fast your tests run)

Examine breakpoint

You’ll notice that nothing is rendered in the browser, you just have the console to work with. This was particularly useful for testing query selectors to see if you are writing them correctly, as well as trying to figure out what is rendered on the page in general without being able to look at it. This became especially useful when doing integration tests with components that were composed of other components.

Lesson 5: Components are only rendered one level deep

The highest level component in my project was demoComposition, which contains two multiEditTable components, which each contain inputTableCell. During my test of demoComposition, I was trying to interact with the inputs rendered by inputTableCell, but the inputs just weren’t there.

Component not found by query selector

I might be missing something, but this Stack Overflow post seems to indicate there this is some kind of “shallow” rendering mode and there is some way to render all of the components. I didn’t figure it out, so I left it at just testing that the top level components are rendered. This was a shame since I couldn’t try out testing the interaction with the Apex controller. This is something I would like to dig into more to see what options I have in terms of rendering more of the hierarchy, or even just mocking out the child components.

Conclusions

There is still a lot more to explore with LWC. For example, I’d like to do more with the @wire decorator and mixing aura components with LWC. But while there a still some kinks to work out, I am very excited to start adding LWC to production applications. Not only will this open up Salesforce platform development to more traditional JavaScript developers, but it also makes front-end development much more of a joy for those us already in the system. The tools provided with LWC will help automate some of the simpler front-end QA that we usually manually perform, help developers make front-end changes with more confidence that they aren’t causing unexpected bugs, and lead to generally cleaner and more modular code. I encourage every Salesforce developer to give Lightning Web Components a try and I would love to see what the community is making so feel free to shoot me an email or a tweet to share some code.