Chutzpah.json Configuration File Examples
Imagine you have a project with the following source and test files:
fakeSite
| .eslintrc
| Chutzpah.json
|
+---source
| | add2.js
| | add2.misplaced_in_source.test.js
| | double.js
| |
| \---exponential
| square.js
|
\---tests
| add2.test.js
| double.test.js
|
+---exponential
| square.test.js
|
\---misplacedTests
double.wrongFolder.test.js
If that's hard to imagine, clone the khutzpa repo and you will literally have this site on your hard drive. It's in the ./tests/fakeSite
folder. The tests
folder in the root of that repo.
In this testing site, we're almost following a specific convention:
For each
.js
source file in the foldersource
(andsource
's subfolders), have a separate file with the same name but a.test.js
extension in the same folder hierarchy, using atests
folder in place ofsource
.
For instance:
-
./source/add2.js
is the source -
./tests/add2.test.js
is the test.
We have that for all of our source files, above.
But we also have some test files in the "wrong" places.
One test is in the source
folder.
./source/double.wrongFolder.test.js
One test is in tests
but not in the right subfolder.
./tests/misplacedTests/more.add2.test.js
More, we've got a broken test. In add2.misplaced_in_source.test.js
, we test the add2broken
method from add2.js
. That method is broken, or at least doesn't add two to the value passed in.
Let's start with a near simplest-case Chutzpah.json file for this website and see what happens.
{
"References": [
{
"Path": "/source/",
"Includes": "**/*.js",
"Excludes": ["**/coverage/**"]
}
],
"Tests": [
{
"Path": "/tests/",
"Includes": "**/*.test.js",
"Excludes": ["**/coverage/**"]
}
],
"CodeCoverageIncludes": ["**/*.js"],
"CodeCoverageExcludes": ["**/*.test.js"]
}
A few things to note.
- You can use an array of strings or a single string for
Includes
and/orExcludes
if you only have one entry. - Both
References
andTests
haveExcludes
of**/coverage**
. That's to ignore any coverage report folders that khutzpa creates, as they will include the JavaScript files used to in the process of displaying the report, likeprettify.js
andsorter.js
. We don't want those to factor into our tests or coverage reports.
If we use that configuration file, we'll see the following after running khutzpa:
fakeSite> khutzpa .
START:
square.test.js
square function in global scope
√ should return a value that squares that which was pushed in
double.wrongFolder.test.js
double function in global scope
√ should return a value double that pushed in
add2.test.js
add2 function in global scope
√ should return a value that is 2 greater than that which was pushed in
√ should return a value that is 2 when zero is sent
add2.misplaced_in_source.test.js
add2broken function in global scope
× (should FAIL) should return a value that is 2 greater than that which was pushed in
double.test.js
double function in global scope
√ should return the value unmodified when fed a string
Finished in 0.026 secs / 0.005 secs @ 12:26:23 GMT-0400 (Eastern Daylight Time)
SUMMARY:
√ 5 tests completed
× 1 test failed
FAILED TESTS:
add2.misplaced_in_source.test.js
add2broken function in global scope
× (should FAIL) should return a value that is 2 greater than that which was pushed in
Chrome 114.0.0.0 (Windows 10)
Expected 27 to be 7.
at <Jasmine>
at UserContext.<anonymous> (source/add2.misplaced_in_source.test.js:13:28)
at <Jasmine>
=============================== Coverage summary ===============================
Statements : 76.47% ( 13/17 )
Branches : 68.75% ( 11/16 )
Functions : 80% ( 4/5 )
Lines : 81.25% ( 13/16 )
================================================================================
::RESULTS::
[ 1 ]
::eoRESULTS::
Note that if we check for the call's output, it's non-zero. The first non-zero return value from khutzpa's calls will be returned, here 1
taken from the single-element array, [ 1 ]
. A non-zero value means failure.
To access that value on the command line, you can use the following:
Powershell
echo $LastExitCode
1
Windows Command Prompt:
echo %ERRORLEVEL%
1
Wait, so if we have a Path
in Tests
limiting spec files to the /tests/
folder, why is khutzpa running tests from add2.misplaced_in_source.test.js
that's in /source/
?
add2.misplaced_in_source.test.js
add2broken function in global scope
× (should FAIL) should return a value that is 2 greater than that which was pushed in
It's because the test file got caught by our References
section. When add2.misplaced_in_source.test.js
is loaded, the tests will be run.
(And test files caught like this will not always run successfully, even if the tests "should" pass, depending on the order that the files are listed and loaded in References
. If something the test needs is loaded later, bam, fail. Putting the file in Tests
ensures it's loaded after References
. Aka, don't load tests by putting them in References
.)
"References": [
{
"Path": "/source/",
"Includes": "**/*.js",
"Excludes": ["**/coverage/**"]
}
],
We can exclude add2.misplaced_in_source.test.js
a few ways.
"References": [
{
"Path": "/source/",
"Includes": "**/*.js",
"Excludes": ["**/add2.misplaced_in_source.test.js", "**/coverage/**"]
}
],
Note that we've added the file name with a globstar (**
) to say, "No matter where in the folder hierarchy you find this file name, exclude it." Unless there's another file by the same name that should be included, that's usually safe to do, and ensures moving a file or configuration file to another folder won't "lose" it.
Now the results won't include the misplaced tests.
START:
square.test.js
square function in global scope
√ should return a value that squares that which was pushed in
double.test.js
double function in global scope
√ should return the value unmodified when fed a string
add2.test.js
add2 function in global scope
√ should return a value that is 2 greater than that which was pushed in
√ should return a value that is 2 when zero is sent
double.wrongFolder.test.js
double function in global scope
√ should return a value double that pushed in
Finished in 0.025 secs / 0.004 secs @ 13:02:37 GMT-0400 (Eastern Daylight Time)
If we're careful with the convention that all test files will end with *.test.js
, we could simply exclude all of the files with this double-extension from the /source/
, if any, to be safe.
"Excludes": ["**/*.test.js", "**/coverage/**"]
Same output as when we excluded the file by name.
Remember that single asterisks are limited to single folders and need the globstar prefix to be found anywhere other than the root. (A great primer on glob selector format is here, at node-glob.)
There are three properties related to code coverage in a Chutzpah.json configuration file.
CodeCoverageIncludes
CodeCoverageExcludes
CodeCoverageIgnores
Note that there is currently no distinction between CodeCoverageExcludes
(2) and CodeCoverageIgnores
(3) in khutzpa. In Chutzpah, the difference is described as follows:
CodeCoverageIgnores: This is an advanced property to use if you are having issues with CodeCoverageExcludes. When you list a file in CodeCoverageExcludes, Chutzpah will not even instrument that file using the Blanket.js engine. This can give performance improvements but in some situations can change the loading order of your files. This can cause test failures for some people. In that situation, CodeCoverageIgnores is the right option. It still lets the coverage engine instrument file but the results of coverage are ignored.
We're not using Blanket any more, and I'm honestly not 100% sure what "instrument" means in this context. Maybe we were loading the file into test runs before but now we're not?
In khutzpa, for backwards compatibility, 2 & 3 are grouped together and the sum total treated as CodeCoverageExcludes
.
If you don't have a value for CodeCoverageIncludes
, you'll currently still see a Coverage Summary with Unknown
percentages in your results.
=============================== Coverage summary ===============================
Statements : Unknown% ( 0/0 )
Branches : Unknown% ( 0/0 )
Functions : Unknown% ( 0/0 )
Lines : Unknown% ( 0/0 )
================================================================================
These results will not prevent an otherwise successful khutzpa test run.
Together, these properties work essentially like a single entry in the References
or Tests
section -- you can insert multiple glob format strings in each array, but the three properties only give you one "selector" total, so to speak.
So, for us, if we want to coverage check just the items in source
, we need to add...
"CodeCoverageIncludes": ["**/*.js"],
khutzpa assumes you only want coverage with items in References
, and the Includes and Excludes selectors here can only produce subsets of those files.
To show how this works, let's return to the original configuration file that doesn't exclude **/*.test.js
in the Excludes
for References
.
"References": [
{
"Path": "/source/",
"Includes": "**/*.js",
"Excludes": ["**/coverage/**"]
}
],
Now let's run a normal run (we could also run with /coverage
, but that currently only creates an html report. It doesn't output a summation to the command line).
> khutzpa .
...
=============================== Coverage summary ===============================
Statements : 82.6% ( 19/23 )
Branches : 68.75% ( 11/16 )
Functions : 87.5% ( 7/8 )
Lines : 86.36% ( 19/22 )
================================================================================
Those are pretty good numbers. Let's drill into the report to find out what it found.
File | Statements |
|
Branches |
|
Functions |
|
Lines |
|
---|---|---|---|---|---|---|---|---|
add2.js | 100% | 6/6 | 62.5% | 5/8 | 100% | 2/2 | 100% | 6/6 |
add2.misplaced_in_source.test.js | 100% | 6/6 | 100% | 0/0 | 100% | 3/3 | 100% | 6/6 |
double.js | 100% | 3/3 | 100% | 4/4 | 100% | 1/1 | 100% | 3/3 |
Interestingly, our .test.js file seems to be completely tested! I think that's because, during a test, every line in the test file was run. 😕🤔
But that still is unfair. We shouldn't get credit for a test running itself. Let's exclude those from our runs.
Just add this line to your file:
"CodeCoverageExcludes": ["**/*test.js"]
The coverage reported is a little lower, like we expected.
=============================== Coverage summary ===============================
Statements : 76.47% ( 13/17 )
Branches : 68.75% ( 11/16 )
Functions : 80% ( 4/5 )
Lines : 81.25% ( 13/16 )
================================================================================
Let's find out why.
File | Statements |
|
Branches |
|
Functions |
|
Lines |
|
---|---|---|---|---|---|---|---|---|
add2.js | 100% | 6/6 | 62.5% | 5/8 | 100% | 2/2 | 100% | 6/6 |
double.js | 100% | 3/3 | 100% | 4/4 | 100% | 1/1 | 100% | 3/3 |
Sure enough, we lost our coverage credit for the test file, but that's only fair.
When our tests included the file add2.misplaced_in_source.test.js
we received failing return values because that file includes a broken test. When we removed that test file from our coverage report, all tests passed.
That's cool, but if the reason we don't have a failing test is because we removed a test and are no longer testing what's broken, that would be a false sense of achievement.
Luckily there's another way to measure success against a test suite: Measuring code coverage. This tells us whether we have tests for every piece of logic in our code. This is a really tedious task, and automating it makes measuring it practically possible.
Let's ensure we fail a test suite if coverage isn't high enough to be reasonably comfortable that we've tested all of our files fairly thoroughly.
We do that by adding a value for CodeCoverageSuccessPercentage
. A good rule of thumb is to have 80% coverage, though this will always be a judgement call depending on the state and architecture of your code.
"CodeCoverageSuccessPercentage": 80
NOTE: The CodeCoverageSuccessPercentage
property currently only works when you use the /coverage
command-line property. Using CodeCoverageSuccessPercentage
with the default khutzpa call will return coverage values but will not fail a run if CodeCoverageSuccessPercentage
isn't met.
Let's try with a coverage percentage of 20.
"CodeCoverageSuccessPercentage": 20
The results are:
fakeSite> khutzpa . /coverage
=============================== Coverage summary ===============================
Statements : 64.7% ( 11/17 )
Branches : 56.25% ( 9/16 )
Functions : 60% ( 3/5 )
Lines : 68.75% ( 11/16 )
================================================================================
::RESULTS::
[ 0 ]
::eoRESULTS::
That results a value of zero -- all percentages are above 20, so it passes.
Let's try 80%, however.
"CodeCoverageSuccessPercentage": 80
Now the suite fails.
fakeSite> khutzpa . /coverage
08 06 2023 23:18:06.104:ERROR [coverage]: Chrome 114.0.0.0 (Windows 10): Coverage for statements (64.7%) does not meet global threshold (80%)
08 06 2023 23:18:06.107:ERROR [coverage]: Chrome 114.0.0.0 (Windows 10): Coverage for branches (56.25%) does not
meet global threshold (80%)
08 06 2023 23:18:06.109:ERROR [coverage]: Chrome 114.0.0.0 (Windows 10): Coverage for lines (68.75%) does not meet global threshold (80%)
08 06 2023 23:18:06.110:ERROR [coverage]: Chrome 114.0.0.0 (Windows 10): Coverage for functions (60%) does not meet global threshold (80%)
=============================== Coverage summary ===============================
Statements : 64.7% ( 11/17 )
Branches : 56.25% ( 9/16 )
Functions : 60% ( 3/5 )
Lines : 68.75% ( 11/16 )
================================================================================
::RESULTS::
[ 1 ]
::eoRESULTS::
And there we have it... with /coverage
and a CodeCoverageSuccessPercentage
higher than at least one of the values in the summary, we have a 1
returned, and the suite fails.