Is there a correct procedure to write unit tests for components which import localised string modules?
I've written a very simple test:
import React from "react";
import { NumberEdit } from "./NumberEdit";
import renderer from "react-test-renderer";
test("input changes when number entered", () => {
const component = renderer.create(
<NumberEdit valueChanged={console.log} />
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
Which fails in NumberEdit on this line:
import * as strings from "AdmWebpartLibraryStrings";
Cannot find module 'AdmWebpartLibraryStrings' from 'NumberEdit.tsx'
I am unable to mock the entire component as that would render the test pointless. To my understanding the localisation modules are inserted by Gulp during the build process. Perhaps I can get Jest to utilise the relevant parts of this, or perhaps there is a way of getting Jest to 'create' modules?
To be clear, by localised strings I mean the string modules defined in config.json under localizedResource, and their respective JS files/TS declarations.
Thank you for reporting this issue. We will be triaging your incoming issue as soon as possible.
@JakeStanger said:
To my understanding the localisation modules are inserted by Gulp during the build process.
I'm not familiar with anything other than testing the localization using the flag on the gulp serve command. The code is there in the build toolchain, but that's not something that's been documented or discussed in the past.
@JakeStanger said:
I am unable to mock the entire component as that would render the test pointless. Perhaps I can get Jest to utilise the relevant parts of this, or perhaps there is a way of getting Jest to 'create' modules?
Unclear what you mean by "create"... the localization files are just JS returning a default object... why can't you mock that?
I'm not sure how the toolchain handles the localised string modules, but it does something weird with them. To mock it, I'd have to mock a module which as far as Jest is concerned does not exist.

Is there a way the module can be mocked, despite this?
@JakeStanger said:
I'm not sure how the toolchain handles the localized string modules, but it does something weird with them.
SPFx at runtime determines what localization file it should load based on what's in the manifest & the current user + current site locale settings. It uses that to load the localization when it loads the bundle. IOW, it doesn't happen within the toolchain, it happens at runtime.
@JakeStanger said:
Is there a way the module can be mocked, despite this?
I don't know... haven't had to do that with Jest. I think this is more of a Jest question than an SPFx question tho...
I know the actual string object is injected at runtime, but surely that can't be true for the module? The module declared when the solution is scaffolded:
declare module 'AdmWebpartLibraryStrings' {
const strings: IAdmWebpartLibraryStrings;
export = strings;
}
must exist when gulp builds the package, since otherwise TSC would throw an error.
It seems odd that nobody else has hit this issue; are localization strings something most devs don't typically bother with?
I know somebody had the issue here, although that was due to the underlying library using strings so they were able to mock the entire component.
If you still feel this is more of a Jest issue, I'll take the issue either to the jest-preset-spfx-react16 (which I'm using) or Jest itself depending on where you feel is more suitable.
I'd argue given that the issue seems to be with the way the framework works and it's a non-standard issue, it's more of an SPFx issue that needs a proper workaround, and to be properly documented.
I'm certain I've used tests on a component that's been localized, but I never test the localized languages as that's not the SUT... that's SPFx and it isn't something to test.
_In fact, I don't test anything in the SPFx components... that's my recommend guidance in my course as well: don't test anything in an SPFx component (anything that comes from a SharePoint NPM package. Too much of the SPFx packages expect to only run within the SPFx runtime. There's nothing you can do to impact how that works, so I see no reason to include it in tests (not to mention it dramatically slows down testing having to go through the entire rebuild). I only test my React components & services._
@JakeStanger said:
If you still feel this is more of a Jest issue, I'll take the issue either to the jest-preset-spfx-react16 (which I'm using) or Jest itself depending on where you feel is more suitable.
I wouldn't post the question to the present... I'm the only one who looks at that issue list (that's my project) and as you can see from this thread, I've got no suggestions :) so you'd do better going to the Jest forums.
This would suggest I need to review how we're going about importing strings. Since every component we develop is designed for SPFx, we've been importing strings from inside each component.
It would appear a better structure would be to import strings at the top level and pass them down as props to separate them from the framework. Sounds like common sense when you write it down really.
I'll close this for now and have a play around. Cheers for your help.
@JakeStanger said:
It would appear a better structure would be to import strings at the top level and pass them down as props to separate them from the framework.
Before you go down that route, consider implementing a service locator pattern as a way to get the strings (or other SPFx specific resources). Will make things MUCH more testable and less spaghetti code.
This is the pattern I use when one of my React components must get data from a service provided via SPFx (AADHttp/Graph/SPREST/page context). I don't pass those into the components as props, rather a singleton service sitting off to the side grabs what I needs when needed. If not running in SharePoint, it can grab a mock service.
Yes that makes a lot of sense. Will do, thanks!
While restructuring is the better solution, it's a longer term goal. Sometimes you need short-term solutions. I present to you pre-test.js, which possibly breaks every sensible NodeJS rule and convention out there, but it works:
const fs = require("fs");
const config = JSON.parse(fs.readFileSync("./config/config.json").toString());
const indexJs = "module.exports = {}";
const packageJson = stringModule => `{"name":"${stringModule}","main":"index.js"}`;
Object.keys(config.localizedResources).forEach(stringModule => {
fs.mkdirSync(`node_modules/${stringModule}`);
fs.writeFileSync(`node_modules/${stringModule}/index.js`, indexJs);
fs.writeFileSync(`node_modules/${stringModule}/package.json`, packageJson);
});
Quick explanation: literally create modules in node_modules before tests run. "Ideally" this should have a post-test to delete the directories.
That's an interesting solution... I see what you're doing... wondering if there's a better way to tell Jest where to find the imports, similar to how you can tell webpack...
There's definitely a better answer out there.
For anybody who stumbles upon this, I'll link to here where further discussion has taken place.
Issues that have been closed & had no follow-up activity for at least 7 days are automatically locked. Please refer to our wiki for more details, including how to remediate this action if you feel this was done prematurely or in error: Issue List: Our approach to locked issues