DEV Community

Cover image for Things I learned after writing tests for JS and HTML page
Yuki Cheung
Yuki Cheung

Posted on

Things I learned after writing tests for JS and HTML page

In these weeks, I always want to learn about writing tests using Jest, Mocha or other stuffs. Unfortunately, I don't have any React apps that want to test these days but I have chances to figure out how to test HTML page with pure JavaScript.

I found most tutorials are using Jest with React or other JavaScript frameworks. Is it really possible to test HTML page with Jest? Yes!

How to start

View the demo here for my sample HTML page. Just a simple page to fetch JSON, show a list based on it and a button to show/hide translations.

In your root folder, create a package.json like this, and run npm install in console.



{
    "scripts": {
        "test": "jest --watch",
        "coverage": "jest --coverage"
    },
    "devDependencies": {
        "jest": "^23.6.0"
    }
}



Enter fullscreen mode Exit fullscreen mode

When you finished, you can start testing your app! Create a file <YOUR-FILENAME>.spec.js and start testing like:



const fs = require('fs');
const path = require('path');
const html = fs.readFileSync(path.resolve(__dirname, '../index.html'), 'utf8');

jest
    .dontMock('fs');

describe('button', function () {
    beforeEach(() => {
        document.documentElement.innerHTML = html.toString();
    });

    afterEach(() => {
        // restore the original func after test
        jest.resetModules();
    });

    it('button exists', function () {
        expect(document.getElementById('disable')).toBeTruthy();
    });
});



Enter fullscreen mode Exit fullscreen mode

Save it and run npm test!

View my GitHub here for codes

What I learned

1. Difference between testing HTML and React page

In React, you can use Enzyme to shallow the component and get state for testing. But in HTML and JavaScript page, the only thing you can test is the classes, content and function output. When you are used to test by states, it might be not so convenient to test HTML page.

As pure JS did not export anything for Jest to test, you also need to add this at the end:



if (typeof exports !== 'undefined') {
    module.exports = {
        getItem,
        setItems,
        triggerItem
    };
}


Enter fullscreen mode Exit fullscreen mode

Then Jest can import/export functions for testing.

For HTML, you cannot directly import it like React component. You need to add this snippet before tests, to import the whole HTML:



const fs = require('fs');
const path = require('path');
const html = fs.readFileSync(path.resolve(__dirname, '../index.html'), 'utf8');

jest
    .dontMock('fs');



Enter fullscreen mode Exit fullscreen mode

or write inline HTML inside tests. For example:



const html = document.createElement('div');
html.innerHTML = `<div class="lang-grid" id="language">This is a sample</div>`;
document.body.appendChild(div);


Enter fullscreen mode Exit fullscreen mode

2. Special way to test asynchronous code

It is still easy to test basic thing, like checking the menu button will appear in mobile view using Jest. Like:



    it('menu button exists', function () {
        expect(document.getElementById('menu-btn')).toBeTruthy();
    });



Enter fullscreen mode Exit fullscreen mode

But for asynchronous code, like Promise, you need to use different approach.

The most important thing is to add done() in each test.




    it('get same items from json', function (done) {
        fetch.mockResponse(JSON.stringify(json))
        const {getItem} = require('../scripts/main.js');

        getItem().then(res => {
            expect(res).toEqual([{
                "phase": "Entschuldigung!",
                "trans": "Excuse me. [as in may I have your attention]."
            },
            {
                "phase": "Sprechen Sie Englisch?",
                "trans": "Do you speak English?"
            }])

            expect(res.length).toEqual(2);
            done();
        })
        .catch(err => console.log(err))
    });



Enter fullscreen mode Exit fullscreen mode

Just like what Jest documentation said, it is important to add done() in the test. Otherwise, it may have wrong results.

After you added done(), it will wait till your async call to be resolved and get the expected result.


3. Check coverage using Jest

Jest has built-in coverage function, you can call it using jest --coverage. Then you can see your reports in coverage/lcov-report/index.html. The chart is very useful and inform you which code have not tested.

Coverage tool
(Why it is not 100% in branches? Because I skipped the test of exporting module statement at the end. )

I have not use coverage tool before, that's why I am motivated when I saw my code changed from red to green!


Is testing fun?

Well, it may not be fun, but surely is satisfactory when I saw my code changed from RED to GREEN.

Do you have any suggestions to my flow? Or any ideas on testing? Feel free to drop me a line here :)

Top comments (10)

Collapse
 
shermayster profile image
Pavel Shermayster

Try to avoid testing implementation details, it’s better to focus on testing functionality.

In you case I would test result in UI, rather than “get item” function. In that case, you can make refactoring, change name of the function, transfer to different module, and tests will help you to validate that you didn’t break functionality of the code.

Then you testing implantation, you need to change your tests with your code, so it’s not very helpful.

Collapse
 
snowleo208 profile image
Yuki Cheung • Edited

Thanks for your reply!

I am still a beginner of testing and I found that it is hard to learn how to write good tests. You reply is extremely helpful. Thanks! :)

Collapse
 
duranenmanuel profile image
Enmanuel Durán

Great article!

I am curious about one thing, how did you deal with all the unsupported features from jsdom such as MutationObserver, or the inability to access input fields by name without using querySelector like formName.inputName.value. Did you have to manually install polyfills for each or maybe you did something else?

Collapse
 
apihlaja profile image
Antti Pihlaja

Wait a minute.. is document exposed by jest by default? Or how that works? 🤯

Collapse
 
antonmelnyk profile image
Anton Melnyk

It is exposed if you set

testEnvironment: 'jsdom'

as an option.

Collapse
 
thawkin3 profile image
Tyler Hawkins

Thanks Yuki for writing this! I had a similar question of how to test an HTML file with vanilla JavaScript and no UI frameworks, and your article gave me a really good starting point.

I ended up finding a way to do this with Jest and Kent Dodd's DOM Testing Library, and I wrote about it here: dev.to/thawkin3/how-to-unit-test-h...

Collapse
 
snowleo208 profile image
Yuki Cheung

Yes, testing library is quite good and it forced you not to use shallow or create lots of mocks for different functions.

I am using react-testing-library currently and it is fantastic! It is great to learn how to write unit tests using testing library and learn those best practices :)

Collapse
 
axelkirk profile image
KIRK Axel

Many thanks for this article, I was struggling in so many point you highlight :D
You make my week :D

Collapse
 
ben profile image
Ben Halpern

Super helpful Yuki!

Collapse
 
snowleo208 profile image
Yuki Cheung

Thanks :)