快乐双彩开奖结果今天:Jest + jQuery for testing a vanilla “app”

May 15th, 2014. Tagged: JavaScript, tools

Jest is a new javascript testing tool announced today. I thought I'd take it out for a spin testing the UI of a simple vanilla JS app - no CommonJS modules, no fancy stuff. Just old school JavaScript. Granted, it's probably not what the tool was optimized to do, but it totally worked.

The app

It's a simple HTML page with inline CSS and JS that validates username and password and paints some of the UI in red if there's a validation error. Give it a try

Markup:

<p class="error error-box hidden" id="err">Please fill in the required fields</p>
<form onsubmit="return validateSubmit(this)" method="post" action="/cgi-bin/perlbaby.pl">
  <ul>
    <li><label id="username-label" for="username">Username</label>
        <input id="username"></li>
    <li><label id="password-label" for="password">Password</label>
        <input id="password"></li>
  </ul>
  <button type="submit" id="button">go</button>
</form>

CSS:

.hidden {display: none}
.error {color: red}
.error-box {border: 1px solid red}

When the user submits the form, the function validateSubmit() is called to do the validation. There's no framework so everything is pretty old school:

function validateSubmit(f) {
  var validates = true;
  ['username', 'password'].forEach(function(field) {
    if (!document.getElementById(field).value) {
      validates = false;
      document.getElementById(field + '-label').className = 'error';
    } else {
      document.getElementById(field + '-label').className = '';
    }
  });
  document.getElementById('err').className = validates
    ? 'hidden' 
    : 'error error-box';
 
  if (validates) {
    // fancy stuff goeth here
  }
 
  return false;
}

Actually it was even older school, but the test didn't quite work because JSDOM which is used behind the scenes for the DOM stuff doesn't support ancient stuff like accessing form elements of the sort: document.forms.username. JSDOM also don't seem to support classList property at the moment, which is a bummer, but I'm sure will be added eventually. Anyway.

Feel free to play with the page and try to submit emtpy fields to see the UI changes

OK, so how do you test that this page behaves as expected. Enter Jest.

Jest

To install Jest, go

$ npm install -g jest-cli

You then need to create a package.json file where your app lives, like:

{
  "scripts": {
    "test": "jest"
  }
}

Now you're ready to run tests!

$ cd ~/apps/app

$ mkdir __tests__

$ npm test

> @ test ~/apps/app/jester
> jest

Found 0 matching tests...
0/0 tests failed
Run time: 0.596s

Cool, it works! Only there are no tests to run.

A test example

If you're familiar with Jasmine for JS testing... well, Jest extends that so the syntax is the same. Here's a barebone minimal example:

describe('someName', function() {
  it('does stuff', function() {
    // ...
    expect(true).toBeTruthy();
  });
});

Put this in your app's __tests__ directory so Jest knows where to find and run:

$ npm test

> @ test ~/apps/app/jester
> jest

Found 1 matching tests...
 PASS  __tests__/example.js (0.016s)
0/1 tests failed
Run time: 1.305s

Or how about making the test fail, just for kicks:

describe('someName', function() {
  it('does stuff', function() {
    // ...
    expect(true).toBe(1);
  });
});

Running...

$ npm test

> @ test ~/apps/app/jester
> jest

Found 1 matching tests...
 FAIL  __tests__/example.js (0.017s)
● someName ? it does stuff
  - Expected: true toBe: 1
        at Spec. (~/apps/app/jester/__tests__/example.js:4:18)
        at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
1/1 tests failed
Run time: 1.405s

Not bad. Now let's do a real example.

Testing the vanilla

The thing about Jest is that it mocks everything. Which is priceless for unit testing. But it also means you need to declare when you don't want something mocked. Starting the new test with:

jest
  .dontMock('fs')
  .dontMock('jquery');

"Huh?!" you say. jQuery? Yup, I used jQuery to do the DOM-y stuff in the test. Like submit the form and check for class names, fill out the form, and... no, that's about it. You, of course, can use any library that JSDOM can handle.

The magic of Jest is in its use of require() for all the mocking. Read more here. So any module you require will be mercilessly mocked unless you say dontMock().

Moving on.

I'll fetch the markup (that includes the inline JavaScript) so I can test it later. Oh, and require jQuery:

var $ = require('jquery');
var html = require('fs').readFileSync('./app.html').toString();

Now, you know the "template" for a new test. Let's have two of these:

describe('validateSubmits', function() {
  
  it('shows/hides error banner', function() {
 
    // ... test here
 
  });
  
  it('adds/removes error classes to labels', function() {
    
    // ... test here
 
  });
 
});

test #1

First set the content of the empty document that the framework has created with the contents of the app read from disk:

document.documentElement.innerHTML = html;

Next, checking the initial state. In the initial state the error message is hidden with a CSS class name .hidden since there are no errors. So here comes the jQuery magic combined with Jasmine's:

// initial state
expect($('#err').hasClass('hidden')).toBeTruthy();

Next, submit the form without filling it out. Error state ensues. The error message paragraph is now displayed because our app removed the .hidden class:

// submit blank form, get an error
$('form').submit();
expect($('#err').hasClass('hidden')).toBeFalsy();

Finally, test that the error message is again hidden after the form is filled out and submitted:

// fill out completely, error gone
$('#username').val('Bob');
$('#password').val('123456');
$('form').submit();
expect($('#err').hasClass('hidden')).toBeTruthy();

Test #2

The second test is similar, only this time we're checking if the form labels have .error class which makes 'em all red. Here goes:

document.documentElement.innerHTML = html;
 
// initially - no errors
expect($('#username-label').hasClass('error')).toBe(false);
expect($('#password-label').hasClass('error')).toBe(false);
 
// errors
$('form').submit();
expect($('#username-label').hasClass('error')).toBe(true);
expect($('#password-label').hasClass('error')).toBe(true);
 
// fill out username, missing password still causes an error
$('#username').val('Bob');
$('form').submit();
expect($('#username-label').hasClass('error')).toBe(false);
expect($('#password-label').hasClass('error')).toBe(true);
 
// add the password already
$('#password').val('123456');
$('form').submit();
expect($('#username-label').hasClass('error')).toBe(false);
expect($('#password-label').hasClass('error')).toBe(false);

The full source is here

Thanks!

Thanks for reading! Now, I'm sorry to inform you, you have no excuse not to write tests. Even this old school page can be tested, imagine what you can do with your awesome fancy JS modules!

Tell your friends about this post: Facebook, Twitter, Google+

Sorry, comments disabled and hidden due to excessive spam.

Meanwhile, hit me up on twitter @stoyanstefanov


  • 76人参加高山滑雪跨界跨项选材测试 或进冬奥备战梯队 2019-05-18
  • 不是特效!上港队长一脚踢中海鸥 这脚法简直坑爹 2019-05-08
  • 新疆坚决打好污染防治攻坚战 2019-05-07
  • 高考在即,晋中市招生考试管理中心提醒广大考生及家长要把握好高考“四个趋势” 2019-04-25
  • 运城市在长三角招商引资149.9亿元 2019-04-08
  • 济南五胞胎雪虎宝宝亮相 四雌一雄萌态十足 2019-04-08
  • 去产能迎年中考 煤炭、钢铁企业债务问题依然存在 2019-04-07
  • 最高检等四部门出台意见 指导依法办理恐怖活动和极端主义犯罪案件 2019-04-07
  • 广州市黄埔区人民法院公告专栏 2019-03-28
  • 这个“海之宁”是个死抱着相对论旧谬误不放,疯狂反对科学新真理的跳梁“小丑”,这个跳梁“小丑”根本就不懂得尊重客观事实及其规律,总是无视、脱离、歪曲客观... 2019-03-22
  • 如何理解孔子这句话?北大教授胡军动情论生死 2019-03-21
  • 海外舆论关注中国最新军备 称赞习近平主席强军号令 2019-03-19
  • 女性之声——全国妇联 2019-03-19
  • 美司法部科米在希拉里邮件门调查中存在过失 2019-03-17
  • “四新”彰显党的十九大思想灵魂和精髓要义 2018-08-17
  • 44| 974| 767| 684| 515| 365| 178| 696| 674| 457|