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.

Lightning Web Components - First Impressions

I am not a huge of fan of Aura, at least conceptually. As a tool it is fine, but a JavaScript framework seemed like a good opportunity to bridge the gap between Salesforce and non-Salesforce web development. Instead, we got Aura which may as well have been another Salesforce specific language that you would have to learn in order the break into the already niche world of Salesforce development. So when they announced Lightning Web Components (LWC) earlier this month I was skeptical. JavaScript fatigue is bad enough - Salesforce specific JavaScript fatigue seems almost comical.

However, the blog post seemed to indicate that they feel the same way:

Frameworks became the language. React, Angular, and the Lightning Component Framework are all JavaScript frameworks, but they provide such a high level of abstraction that they feel like different languages. As a result, skills were not transferable, and developers were hard to find and ramp up.

And their solution:

Lightning Web Components is the Salesforce implementation of that new breed of lightweight frameworks built on web standards. It leverages custom elements, templates, shadow DOM, decorators, modules, and other new language constructs available in ECMAScript 7 and beyond.

A standards based approach was intriguing to me, so I wanted to give LWC a spin and share what I learned along the way.

Dynamic Multi Line Editable Table

Something I have built over and over again is a mechanism to input multiple lines of input and then submit it to the server for whatever processing. I wanted to be able to feed a component an array of columns and it dynamically builds my table for me. I wanted to able to code something like this:

<c-multi-edit-table 
    column-list='\[{ "label" : "Name", "apiName" : "Name"} , { "label" : "Email", "apiName" : "Email" }, { "label" : "Phone", "apiName" : "Phone" }]'
    title='Attendees'>
</c-multi-edit-table>

<c-multi-edit-table 
    column-list='\[{ "label" : "Fee Name", "apiName" : "FeeName" }, { "label" : "Amount", "apiName" : "Amount" }]'
    title='Estimated Fees'>
</c-multi-edit-table>

And get this: dynamic table screenshot

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

Quick Start: Lightning Web Components

LWC Documentation

GitHub repo with project code

Lesson #1: Naming Matters

I actually learned this last, but I thought it was very important to point out and save someone an hour trying to figure out what’s wrong.

A component is composed of three parts: HTML, JS, and CSS

multiEditTable
|_ multiEditTable.html
|_ multiEditTable.js
|_ multiEditTable.css

Which you can then reference in other components by using

<c-multi-edit-table></c-multi-edit-table>

Turns out that the naming is very important here. The camel casing is parsed and each word is separated with a -. This causes an issue if you start your component with a capital letter like I originally did (i.e. MultiEditTable). In order to reference it you’ll need to use:

<c--multi-edit-table></c--multi-edit-table> <!-- notice the two dashes before "multi" -->

The hard part about working with a pre-release is not knowing what is a bug and what is working as intended. My guess is that the parser is putting a dash before every capital letter, but the autocomplete in the VS Code plugin didn’t pick this up so I spent an hour or so trying to figure out why my component would not render. The only error message I would get is Compilation Failed. I doubt this is the way it is intended to work, so to be safe just use a lower case letter when naming your components.

Lesson #2: Computed Keys Still Not Allowed

One obstacle I always ran into while doing something dynamic like this was working with computed keys within the component markup. Here’s something you can’t do in Aura:

<aura:attribute name="record" type="Object[]"/> 
<aura:iteration items="{!v.columns}" var="column">
    <lightning:input label="{!v.column}" value="{!v.record[column]}"/>
</aura:iteration>

Aura won’t let you bind computed key values (i.e. value="{!v.record[column]}" is invalid). To get around this, I had to create 3 separate components - 1 for the table itself, 1 for each row, and one for each input column within each row. It creates so much code and the components are so tightly coupled that you would never use them separtely. Here’s a basic version of the end product in aura:

-- Table component
<aura:component>
    <aura:attribute name="record" type="Object[]"/> 
    <aura:attribute name="columns" type="String[]"/>
    <aura:iteration items="{!v.records}" var="record">
        <c:InputRow row="{!v.record}" columsn="{!v.columns}"/>
    </aura:iteration>
</aura:component>

-- InputRow Component
<aura:component>
    <aura:attribute name="row" type="Object"/> 
    <aura:attribute name="columns" type="String[]"/>
    <aura:iteration items="{!v.columns}" var="column">
        <c:InputCell record="{!v.row}" field="{!v.column}"/>
    </aura:iteration>
</aura:component>

-- InputCell Component
<aura:component>
    <aura:attribute name="record" type="Object"/> 
    <aura:attribute name="field" type="String"/>
    <aura:attribute name="value" type="String"/>
    <lightning:input value="{!v.value}" onchange="{!c.updateRecord}"> --updateRecord would set the field value on record
</aura:component>

If you could use computed keys, this would actually be fairly straightfoward. Here’s what I tried first using LWC:

<template for:each={record} for:item="record">
    <template for:each={columns} for:item="column">
        <td key={column}>
            <lightning-input name={column} value={record[column]}></lightning-input>
        </td>
    </template>
</template>

Unfortunately this still does not work and the compiler explicitly states that you cannot use computed keys. I was so disappointed that I almost stopped there, but I figured I had gotten this far and I might as well see if LWC would provide other benefits.

Lesson #3: One-Way vs. Two-Way Binding

When I built this table using Aura, I created an array of Objects in the top most component and passed the individual Objects in that array down the hierarchy.

<aura:attribute name="records" type="Object[]"/> 
<aura:iteration items="{!v.records}" var="record">
    <c:InputRow row="record">
</aura:iteration>

In Aura, you can define an attribute as having one way or two binding using {#v.row} and {!v.row} respectively. Two way binding made keep tracking of changes in the rows easy. You can pass an Object all the way down the hierarchy of components and any changes to that Object are automatically propagated back to the parent for you.

With LWC, you only get one-way binding; there’s no option for two-way binding. At first I was frustrated by this because if I wanted to mimic the two-way binding, I thought I would have to deal with broadcasting and handling events. However, the more I thought about it, the more I realized that enforcing one-way binding would lead to less side effects. Components now have to explicitly change their properties instead of having them get changed by another component due to a shared reference to an object where it is likely tracked using a different variable name. In the previous example the component tracked the objects using the variable record, whereas the InputRow component used row. When working with more complex components keeping track of these variable pairings in your head could make debugging tricky.

Lesson #4: Standard Query Selectors

That aside, I still needed to track the changes made between each row. At this point, I didn’t see any of the productivity increases that I was hoping for - I still needed to create multiple levels of components and now I needed build an event so I could capture the row changes. Then I realized that I was still thinking with an Aura mindset.

I started to wonder how I would solve this if I was just building this with vanilla JavaScript. I would probably just use document.querySelectorAll('tr') and just iterate over each input within each row and build an array of objects from there. In LWC, you can do just that. For example, here’s how you can get the inputs from each row:

retrieveRecords() {
    var records = [];
    Array.from( this.template.querySelectorAll("tr.inputRows") ).forEach(row => {
        var record = {};
        Array.from( row.querySelectorAll("input") ).forEach(input => {
            record[input.name] = input.value;
        });
        records.push(record);
    })

    return records;
}

This is where LWC started to click for me. With LWC, I could think with a more standard mindset instead of trying to solve Salesforce specific problems with Salesforce specific tools. More importantly, I could search for my JavaScript questions on Google without having to prepend every query with salesforce.

Lesson #5: More Intuitive Server Calls

Now that I have all the data I need, I needed to submit it to my Apex controller to do whatever processing (i.e. a database insert). This was much clearer in LWC.

In Aura:

submit : function(component, event, helper) {
    var action = component.get("c.submitRecords");
    action.setParams({
        "records": JSON.stringify(component.find("inputTable").get("v.recordList")),
    });

    action.setCallback(this, function(response) {
        var state = response.getState();
        if (state === "SUCCESS") {
            alert('Success!');
        } else if (state === "ERROR") {
            alert('Something went wrong...');
        }
    });

    $A.enqueueAction(action);
}

And the same method in LWC:

import { LightningElement } from 'lwc';
import submitRecords from '@salesforce/apex/MultiEditTableController.submitRecords';

export default class DemoComposition extends LightningElement {
    submit() {
        var allRecords = this.template.querySelector("c-multi-edit-table").retrieveRecords();
        submitRecords({ records: allRecords })
            .then(() => {
                alert('Success!');
            })
            .catch(() => {
                alert('Something went wrong...');
            })
    }
}

I don’t want to argue style or readability, but what I really like about LWC is that it is very clear where everything is coming from. The submitRecords controller method is explicitly referenced in LWC so you know which Apex class it’s referencing. Also because of the way LWC uses import to bring in Apex methods, you could in theory include methods from other Apex classes fairly easily. In Aura, you’d have to create a separate service component that hooked up to another Apex controller and then include that commponent for the sole purpose of accessing those methods.

Next Steps:

After a few hours I was successfully able to create my table. This came as a surprise because we’re still in the pre-release stage and there’s not much documentation out there. LWC’s adherence to web standards made this less of an issue because a lot of the problems I ran into weren’t all Salesforce specific problems. I could not say the same about my initial experience with Aura.

However, the biggest surprise was something I haven’t touched upon yet. In the next post, we’ll cover testing LWC using Jest, all from the comfort of your local machine.