Introduction
Since Visual Studio 2022, JavaScript unit tests have been able to be discovered and run on Test Explorer of Visual Studio easily. That is much more convenient for developing Razor Class Library for Blazor. When I was developing a Razor Class Library that contains JavaScript helper code for use from C# code, I often wanted to execute unit tests for both C# code and JavaScript code. Visual Studio 2022 has made me able to do it.
How to do it?
Suppose you already have a unit test project for a Razor Class Library, particularly for the C# code. In that case, you can make that unit test project do tests for JavaScript code additionally just by adding a few steps like below.
1) Add the <JavaScriptTestFramework>
property in your unit test project file (.csproj) to specify which JavaScript unit test framework you use. And also, add the <JavaScriptTestRoot>
property to specify the relative path where unit test codes are placed.
The following example shows using "mocha", one of the well-known JavaScript test frameworks.
<!-- A .csproj file of your unit test project -->
<Project Sdk="Microsoft.NET.Sdk">
...
<PropertyGroup>
<JavaScriptTestFramework>Mocha</JavaScriptTestFramework>
<JavaScriptTestRoot>tests\</JavaScriptTestRoot>
</PropertyGroup>
...
2) Configure your JavaScript unit test environment. For example, to use "mocha", execute the following commands in a terminal console on the test project directory.
(As a premise, Node.js must be installed in your environment.)
> npm init -y
> npm install --save-dev mocha
And please remember to exclude the "node_modules" folder from the unit test project like this:
<!-- A .csproj file of your unit test project -->
<Project Sdk="Microsoft.NET.Sdk">
...
<!-- 👇 exclude the "node_modules" folder for any purpose. -->
<ItemGroup>
<Compile Remove="node_modules\**" />
<EmbeddedResource Remove="node_modules\**" />
<None Remove="node_modules\**" />
</ItemGroup>
...
3) Write your unit test code according to the JavaScript unit test framework you use.
// ./tests/fooTest.js
const assert = require("assert");
describe("fooTest", function () {
it('foo is bar', function () {
assert.ok("this test will alwasy pass.");
})
});
After doing that, you will see those JavaScript unit tests on the Test Explorer, and you can run those unit tests in Visual Studio's usual way.
For more information, please see also the following link to official document site.
Write test codes by TypeScript
Of course, we can write those JavaScript unit test codes by TypeScript. For example, to do that, execute the following steps.
1) Install the "Microsoft.TypeScript.MSBuild" NuGet package into the unit test project. One of the ways to do that is by executing the dotnet add package
command on the unit test project directory like this:
> dotnet add package Microsoft.TypeScript.MSBuild
2) Add type definition files (.d.ts) for TypeScript code that you need, like this:
> npm install --save-dev @types/mocha @types/node
3) Add the "tsconfig.json" file with the following contents into the unit test project directory to configure the behavior of the TypeScript compiler. (Please tweak the "target" configuration according to your needs.)
// ./tsconfig.json
{
"compilerOptions": {
"target": "ES2017",
"module": "CommonJS",
"strict": true
},
"compileOnSave": true
}
After doing that, you can write unit test codes by TypeScript like this:
// ./tests/fooTest.ts
import * as assert from "assert";
import { describe, it } from "mocha";
describe('fooTest', function () {
it('foo is bar', function () {
assert.ok("this test will always pass.");
})
});
Problem - incompatible module system
By the way, I also write a helper JavaScript code inside a Razor Class Library ― that will be a target of unit tests in this scenario ― by TypeScript.
And the type of module system of them must be "ES Module", not "CommonJS", because those JavaScript codes will be invoked from Blazor applications by using IJSRuntime.InvokeAsync<IJSObjectReferense>("import", <the path of .js file>)
.
For example, the tsconfig.json
in a Razor Class Library project will be like the following configuration.
// tsconfig.json in a Razor Class Library project
{
"compilerOptions": {
...
"module": "ES2015", // 👈 Use the ES Module system, not CommonJS.
...
And the compiled JavaScript code, for example, the "CatClass", will be the following code.
// catClass.js in the Razor Class Library project
export class CatClass {
meows() {
return "Meow!!";
}
}
But unfortunately, "mocha" doesn't work with ES Module mode on Visual Studio expectedly.
I will omit the details, but anyway, you must configure unit test codes for "mocha" that are available for Visual Studio Test Explorer to CommonJS mode.
For example, if you have the test code like below that imports the "catClass.js" as a test target,
// ./tests/catClassTest.ts in the unit test project
...
import { CatClass } from "../../RazorClassLibrary1/catClass";
describe('CatClass', function () {
it('meows', function () {
const cat = new CatClass();
assert.equal('Meow!', cat.meows());
});
});
you will run into the error "SyntaxError: Unexpected token 'export'" when Visual Studio tries to detect unit tests like the above.
(In fact, "mocha" itself can work with ES Module mode since ver.9. But there is no way to enable ES Module mode of "mocha" when Visual Studio detects or runs tests so that it will fail.)
What should I do?
My solution - it's complicated but works
Fortunately, those my JavaScript codes are all written by TypeScript source codes.
So I decided to resolve that problem by compiling all TypeScript files by the "CommonJS" module mode into under the unit test project folder, not only files for the unit test but also for files for the test target.
Let's take a look at what I did.
First of all, I placed the unit test TypeScript files in the root folder of the unit test project.
This is important for compiling at once TypeScript files, including both unit test code and library code (test target).
Of course, I also tweaked the path of importing the test target module (TypeScript file) in the unit test TypeScript files, like this:
// move the ./tests/catClassTest.ts to ./catClassTest.ts
// in the unit test project
...
// 👇 Fix the path of importing the test target module.
//import { CatClass } from "../../RazorClassLibrary1/catClass";
import { CatClass } from "../RazorClassLibrary1/catClass";
...
Next, I added two options to the configuration of the TypeScript compiler "tsconfig.json" in the root folder of the unit test project.
One of them is adding the folder of the Razor Class Library (it is the test target) to the compile target folder.
And another one is the configuration of the output folder explicitly to follow the <JavaScriptTestRoot>
property value in the unit test project file (.csproj).
// ./tsconfig.json in the unit test project
{
"compilerOptions": {
...
// 👇 Add these two options.
"rootDirs": [ "./", "../RazorClassLibrary1" ],
"outDir": "./tests",
...
After I made the above changes and rebuilt the entire solution, I got the result like the following figure.
Finally, I've been able to run the JavaScript unit tests properly on Visual Studio's Test Explorer! 🎉
Conclusion
The ability to run unit tests both for C# and for JavaScript at once on Visual Studio Test Explorer UI is very convenient for me when developing a Razor Class Library for Blazor.
But the JavaScript test framework "mocha" can work only in CommonJS module mode on Visual Studio Test Explorer, even though the module type of the test target JavaScript code is ES Module.
Fortunately, I always write JavaScript code by TypeScript, so I resolved that problem by compiling test target TypeScript files to JavaScript files twice with different module modes ("ES Module" and "CommonJS").
The sample code has been published on the GitHub repository at the following link:
https://github.com/sample-by-jsakamoto/Runs-JavaScript-tests-on-Visual-Studio-2022-Test-Explorer
However, actually, I'm not sure whether my solution is the best.
Really Is there no way to run "mocha" in ES Module mode on Visual Studio Test Explorer?
Or else another JavaScript framework, such as Jest, Jasmine, etc., might allow me to run it in ES Module mode?
If you have a better solution or idea, I appreciate it if you share that.
Learn, Practice, Share. :)
Top comments (0)