Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

should.js : node.js에서 사용할 수 있는 BDD 스타일의 Assertion 모듈

몇일 전에 mocha 테스트 프레임워크를 소개 한 관계로 mocha에서 사용할 수 있는 assetion 모듈인 should.js 를 정리해 봅니다. mocha 글에서도 썼듯이 mocha는 꼭 should.js를 써야하는 것은 아니지만 사용법도 깔끔하고 개인적으로 mocha와 should.js를 만든 TJ Holowaychuk에 대한 신뢰도 있기 때문에 should.js를 사용하고 있습니다.(이러다 LearnBoost 빠돌이 되겠;;;) assertion 모듈이라는 것이 간단한 메서드와 인터페이스만 파악하면 큰 내용은 없기 때문에 정리라고 하기 보다는 should.js 의 문서를 그대로 옮겨놓는 정도이지만 그래서 오히려 문서를 찬찬히 본적도 없기에 한번 찬찬히 보는 셈 치고 정리합니다.

should.js

should.js 는 node.js에서 사용할 수 있는 표현적이고 가독성있으며 테스트 프레임워크에 의존적이지 않은 단언문(assertion) 라이브러리입니다. 객체의 행동을 enumerable않은 단일 getter를 통해 Object를 prototype 확장해서 제공하고 있습니다.

그리고 사실 should.js는 node.js에서 제공하는 Assert 모듈 을 확장했기 때문에 assert 모듈과 동일합니다. 즉 should.equal(str, 'foo')는 assert.equal(str, 'foo')와 똑같이 동작하고 should.AssertionError는 assert.AssertionError와 같습니다. 그래서 node.js의 assert 모듈을 지원하는 어떤 테스트 프레임워크에서도 should.js를 사용할 수 있습니다.

should.js는 소스에서 사용해야 하므로 다음과 같이 로컬설치를 합니다.

$ npm install should

이후의 예제는 소스에서 var should = require('should');로 불러왔다는 가정하에 설명합니다.

should.js의 추가 메서드

앞에서 얘기했듯이 require('should')가 리턴한 객체는 require('assert')가 리턴한 객체를 확장한 것과 같고 assertion을 더 표현적이고 가독성 좋게 하는 추가적인 메서드들을 제공하고 있습니다.

exist (정적)
1
2
3
should.strictEqual(foo, bar)

foo.should.equal(bar)

위 두 코드는 동일합니다. should.strictEqual(foo, bar)는 assert.strictEqual(foo, bar)와 동일합니다. 하지만 should는 Object를 prototype확장했기 때문에 2번라인처럼 작성할 수 있습니다. 이는 더 가독성있게 만들어 줍니다. 하지만 위에서 foo가 존재하지 않는 경우에는 foo is null or undefined이라는 오류가 발생할 것입니다. 객체나 프로퍼티의 존재를 알 수 없는 경우에는 이와 같이 사용할 수 없습니다. 객체나 프로퍼티의 존재 여부는 다음과 같이 exist 메서드로 확인할 수 있습니다.

1
2
3
4
5
6
should.exist({})
should.exist([])
should.exist('')
should.exist(0)
should.exist(null)      // will throw
should.exist(undefined) // will throw

다음과 같이 not을 사용해서 부정문으로 사용할 수도 있습니다.

1
2
3
4
should.not.exist(undefined)
should.not.exist(null)
should.not.exist('')    // will throw
should.not.exist({})    // will throw

이렇게 객체의 존재여부를 확인한 뒤에는 맘편히 객체에 should를 사용할 수 있습니다.

ok

assertion의 진실성(?)은 ok로 검사합니다. 이는 소스 내부에서 그냥 전달한 객체 obj로만 검사합니다. obj == true가 아니라 그냥 obj로 검사한다는 의미입니다.

1
2
3
4
5
6
7
true.should.be.ok
'yay'.should.be.ok
(1).should.be.ok

false.should.not.be.ok
''.should.not.be.ok
(0).should.not.be.ok
true / false

true와 false는 다음과 같이 사용합니다. 이는 Assert === true나 Assert === false로 검사합니다.

1
2
3
4
5
true.should.be.true
'1'.should.not.be.true

false.should.be.false
(0).should.not.be.false
arguments

arguments는 Arguments의 인스턴스인지를 검사합니다.

1
2
3
var args = (function(){ return arguments; })(1,2,3);
args.should.be.arguments;
[].should.not.be.arguments;
empty

empty는 객체의 length가 0인지 검사합니다.

1
2
3
[].should.be.empty
''.should.be.empty
({ length: 0 }).should.be.empty
eql

두 객체가 동등한지 검사합니다. 단순값은 ==로 검사하고 객체일 경우에는 내부 속성을 ===로 검사합니다.

1
2
({ foo: 'bar' }).should.eql({ foo: 'bar' })
[1,2,3].should.eql([1,2,3])
equal

strict 하게 동등성을 비교합니다. 비교는 ===로 검사합니다.

1
2
3
4
5
should.strictEqual(undefined, value)
should.strictEqual(false, value)
(4).should.equal(4)
'test'.should.equal('test')
[1,2,3].should.not.equal([1,2,3])
within

숫자의 범위를 포함하는지 검사합니다.

1
user.age.should.be.within(5, 50)
a

객체의 typeof를 검사합니다.

1
2
user.should.be.a('object')
'test'.should.be.a('string')
instanceof

instanceof를 검사합니다.

1
2
user.should.be.an.instanceof(User)
[].should.be.an.instanceof(Array)
above

주어진 값이 지정한 숫자 값보다 큰 지를 검사합니다.

1
2
user.age.should.be.above(5)
user.age.should.not.be.above(100)
below

주어진 값이 지정한 숫자 값보다 작은 지를 검사합니다. regexp.exec로 검사합니다.

1
2
user.age.should.be.below(100)
user.age.should.not.be.below(5)
match

정규표현식의 match 여부를 검사합니다.

1
username.should.match(/^\w+$/)
length

length 프로퍼티가 존재하는지 검사하고 주어진 숫자값인지를 검사합니다. lengthOf는 length의 별칭입니다.

1
2
user.pets.should.have.length(5)
user.pets.should.have.a.lengthOf(5)
property

property에 첫 파라미터로 전달한 이름의 프로퍼티가 존재하는지 검사하고 두 번째 파라미터를 전달하면 해당 프로퍼티가 두 번재 파라미터 값인지 검사합니다.

1
2
3
4
user.should.have.property('name')
user.should.have.property('age', 15)
user.should.not.have.property('rawr')
user.should.not.have.property('age', 0)
ownProperty

prototype 속성이 아닌 객체 자체가 가진 속성이 존재하는지 검사합니다. 검사는 obj.hasOwnProperty(name)로 수행합니다.

1
({ foo: 'bar' }).should.have.ownProperty('foo')
status(code)

객체가 statusCode 프로퍼티를 가지고 있는지 검사하고 statusCode 프로퍼티가 code값인지를 검사합니다.

1
res.should.have.status(200);
header(field[, value])

객체가 headers 프로퍼티가 존재하는지 검사하고 field로 지정한 프로퍼티가 존재하는지 검사합니다. value를 전달하면 field 프로퍼티가 value값인지 검사합니다.

1
2
3
res.should.have.header('content-length');
res.should.have.header('Content-Length', '123');
res.should.have.header('content-length', '123');
json

Content-Type가 "application/json; charset=utf-8"인지 검사합니다.

1
res.should.be.json
html

Content-Type가 "text/html; charset=utf-8"인지 검사합니다.

1
res.should.be.html
include(obj)

전달한 obj가 indexOf()로 나타낼 수 있는지 검사합니다. 그래서 문자열과 배열이나 indexOf를 구현한 객체에서 사용할 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 배열
[1,2,3].should.include(3)
[1,2,3].should.include(2)
[1,2,3].should.not.include(4)

// 문자열
'foo bar baz'.should.include('foo')
'foo bar baz'.should.include('bar')
'foo bar baz'.should.include('baz')
'foo bar baz'.should.not.include('FOO')
includeEql(obj)

배열에서 사용할 수 있으면 전달한 obj와 같은 객체가 있는지 검사합니다.

1
2
3
[[1],[2],[3]].should.includeEql([3])
[[1],[2],[3]].should.includeEql([2])
[[1],[2],[3]].should.not.includeEql([4])
throw([message])

예외가 던져지는 지 검사합니다.

1
2
3
4
5
6
7
(function(){
  throw new Error('fail');
}).should.throw();

(function(){

}).should.not.throw();

throw()에 message를 전달하면 예외 메시지가 message와 일치하는지 검사합니다. message에는 정규표현식을 사용할 수도 있습니다.

1
2
3
4
5
6
7
(function(){
  throw new Error('fail');
}).should.throw('fail');

(function(){
  throw new Error('failed to foo');
}).should.throw(/^fail/);
keys

전달한 키의 이름을 객체가 모두 가지고 있는지 검사합니다. 배열이나 파라미터로 전달할 수 있는데 객체에서 빠진 키가 있으면 실패합니다.

1
2
3
var obj = { foo: 'bar', baz: 'raz' };
obj.should.have.keys('foo', 'bar');
obj.should.have.keys(['foo', 'bar']);

체이닝과 오류 메시지

앞의 예제에서 보았듯이 assertion은 체이닝으로 연결할 수 있습니다.

1
user.should.have.property('pets').with.lengthOf(4)

이는 다음 코드와 근본적으로는 동일하지만 pet 프로퍼티가 존재하지 않을 수도 있습니다.

1
user.should.have.property('pets').with.lengthOf(4)

다음처럼 and같은 더미 getter를 사용하면 더 명확하게 체이닝할 수 있습니다.

1
user.should.be.a('object').and.have.property('name', 'tj')

assertion이 실패했을 때 더 명확한 내용을 보여주기 위해서 다음과 같이 각 매처에 추가적인 설명 파라미터를 전달하면 실패했을 경우 표시해줍니다. 이 추가적인 설명파라미터는 eql, equal, within, a, instanceof, above, below, match, length, property, ownProperty, include, includeEql 에서 사용할 수 있습니다.

1
2
3
4
5
(1).should.eql(0, 'some useful description')

AssertionError: expected 1 to equal 0 | some useful description
  at Object.eql (/Users/swift/code/should.js/node_modules/should/lib/should.js:280:10)
  ...
Valid HTML5 Valid CSS WCAG 2.1 AA tested