Cypress Assertions ต่าง ๆ ที่ควรต้องรู้ EP4

Sharing is caring!

บทความก่อนหน้านี้ ทำให้เราได้พอที่จะเข้าใจ concept ของการใช้วาน Cypress กันมาบ้างแล้ว บทความนี้จะขอกล่าวถึงเรื่องการ Assertion Expect Case ต่าง ๆ ว่าเขียนอย่างไร และสามารถทำอยากที่เราต้องการได้หรือไม่

อธิบายการ Assertions ของ Automated Testing

assertion (การยืนยัน) ในทางงาน test คือการยืนยันการตรวจสอบของสิ่งที่กำลังสนใจ กับผลลัพธ์ที่คาดหวัง

describe('My First Test', () => {
  it('Does not do much!', () => {
    expect(true).to.equal(true)
  })
})

สร้างไฟล์ my-first-test.spec.js เขียนโค๊ดการเทสง่าย ๆ จากนั้นใส่ assertion expect(true).to.equal(true) โดย true แรกคือการที่เราจะแทนค่าที่เรากำลังสนใจที่จะตรวจสอบ true ค่าที่ 2 คือผลลัพธ์ที่ต้องการให้เป็นผลลัพธ์สำหรับการเทสในเงื่อนไขนี้

BDD Assertions

การทดสอบ case scenario ก่อนการเริ่มเขียนโค๊ดจริง ที่เพิ่มการคาดหวังทดสอบ ในเรื่องพฤษติกรรมของทำงานของ website cypress ใช้ library ของ Chaijs Assertion Library (Expect/Should) ที่มีพร้อมครบในเรื่องการทำ assertion case ต่าง ๆ

const person = {
  name: 'Poolsawat',
  friends: [
    { name: 'Jane' },
    { name: 'Poom', age: 30 },
  ],
  cars: [
    'Nissan',
    'Toyota'
  ],
  getName: () => {
    return "Poolsawat"
  },
  "aa bb" : "Test"
}

// https://github.com/chaijs/chai
describe('BDD Assertions Chaijs', () => {

  it('use expect expected json data', () => {
    expect(person.name).to.not.equal('Jane')
    expect(person.friends[0]).to.deep.equal({ name: 'Jane' })
    expect({ a: { b: ['x', 'y'] } }).to.have.nested.property('a.b[1]')
    expect({ a: { b: ['x', 'y'] } }).to.nested.include({ 'a.b[1]': 'y' })
    expect([1, 2]).to.have.ordered.members([1, 2]).but.not.have.ordered.members([2, 1])
    expect(person.friends[1]).to.have.any.keys('age')
    expect(person.friends[1]).to.have.all.keys('name', 'age')
    expect(person.name).to.be.a('string')

    expect([1, 2, 3]).to.include(2)

    expect(undefined).to.not.be.ok
    expect(true).to.be.true
    expect(false).to.be.false
    expect(null).to.be.null
    expect(undefined).to.be.undefined
    expect(person['aa bb']).to.exist
    expect([]).to.be.empty
    expect(arguments).to.be.arguments

    expect(42).to.equal(42)

    expect({ name: 'Jane' }).to.deep.equal({ name: 'Jane' })
    expect({ name: 'Jane' }).to.eql({ name: 'Jane' })

    expect(10).to.be.greaterThan(5)

    expect(10).to.be.at.least(10)

    expect(5).to.be.lessThan(10)

    expect('test').to.have.length.of.at.most(4)

    expect(7).to.be.within(5, 10)
    expect([1, 2, 3]).to.be.instanceOf(Array)

    expect(person).to.have.property('name')
    expect(person).to.have.deep.property('friends')
    expect('test').to.have.ownProperty('length')

    expect({ a: 1 }).to.have.ownPropertyDescriptor('a')

    expect('test').to.have.lengthOf(4)
    expect('testing').to.match(/^test/)

    expect('testing').to.have.string('test')
    expect({ pass: 1, fail: 2 }).to.have.keys('pass', 'fail')
    const fn = () => {
      throw Error
    }
    //expect(fn).to.throw(Error)

    expect(person).to.respondTo('getName')

    //expect(person).itself.to.respondTo('name')
    expect(1).to.satisfy((num) => { return num > 0 })

    expect(1.5).to.be.closeTo(1, 0.5)

    expect([1, 2, 3]).to.include.members([3, 2])
    expect(2).to.be.oneOf([1, 2, 3])
    //expect(fn).to.change(obj, 'val')
    //expect(fn).to.increase(obj, 'val')
    //expect(fn).to.decrease(obj, 'val')

  })
})
Chainer Example
not expect(name).to.not.equal('Jane')
deep expect(obj).to.deep.equal({ name: 'Jane' })
nested expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]')
expect({a: {b: ['x', 'y']}}).to.nested.include({'a.b[1]': 'y'})
ordered expect([1, 2]).to.have.ordered.members([1, 2]).but.not.have.ordered.members([2, 1])
any expect(arr).to.have.any.keys('age')
all expect(arr).to.have.all.keys('name', 'age')
a(type)
Aliases: an
expect('test').to.be.a('string')
include(value)
Aliases: contain, includes, contains
expect([1,2,3]).to.include(2)
ok expect(undefined).to.not.be.ok
true expect(true).to.be.true
false expect(false).to.be.false
null expect(null).to.be.null
undefined expect(undefined).to.be.undefined
exist expect(myVar).to.exist
empty expect([]).to.be.empty
arguments
Aliases: Arguments
expect(arguments).to.be.arguments
equal(value)
Aliases: equals, eq
expect(42).to.equal(42)
deep.equal(value) expect({ name: 'Jane' }).to.deep.equal({ name: 'Jane' })
eql(value)
Aliases: eqls
expect({ name: 'Jane' }).to.eql({ name: 'Jane' })
greaterThan(value)
Aliases: gt, above
expect(10).to.be.greaterThan(5)
least(value)
Aliases: gte
expect(10).to.be.at.least(10)
lessThan(value)
Aliases: lt, below
expect(5).to.be.lessThan(10)
most(value)
Aliases: lte
expect('test').to.have.length.of.at.most(4)
within(start, finish) expect(7).to.be.within(5,10)
instanceOf(constructor)
Aliases: instanceof
expect([1, 2, 3]).to.be.instanceOf(Array)
property(name, [value]) expect(obj).to.have.property('name')
deep.property(name, [value]) expect(deepObj).to.have.deep.property('tests[1]', 'e2e')
ownProperty(name)
Aliases: haveOwnProperty, own.property
expect('test').to.have.ownProperty('length')
ownPropertyDescriptor(name)
Aliases: haveOwnPropertyDescriptor
expect({a: 1}).to.have.ownPropertyDescriptor('a')
lengthOf(value) expect('test').to.have.lengthOf(3)
match(RegExp)
Aliases: matches
expect('testing').to.match(/^test/)
string(string) expect('testing').to.have.string('test')
keys(key1, [key2], […])
Aliases: key
expect({ pass: 1, fail: 2 }).to.have.keys('pass', 'fail')
throw(constructor)
Aliases: throws, Throw
expect(fn).to.throw(Error)
respondTo(method)
Aliases: respondsTo
expect(obj).to.respondTo('getName')
itself expect(Foo).itself.to.respondTo('bar')
satisfy(method)
Aliases: satisfies
expect(1).to.satisfy((num) => { return num > 0 })
closeTo(expected, delta)
Aliases: approximately
expect(1.5).to.be.closeTo(1, 0.5)
members(set) expect([1, 2, 3]).to.include.members([3, 2])
oneOf(values) expect(2).to.be.oneOf([1,2,3])
change(function)
Aliases: changes
expect(fn).to.change(obj, 'val')
increase(function)
Aliases: increases
expect(fn).to.increase(obj, 'val')
decrease(function)
Aliases: decreases
expect(fn).to.decrease(obj, 'val')

TDD Assertions

คล้ายกับ BDD คือเป็นการเขียน test ต่าง ก่อนการเริ่มเขียน code แต่จะไม่ละเอียดเท่า BDD เพราะไม่รู้ Behavior(พฤติกรรม) ของผู้ใช้งานสามารถใช้ ChaiJs Assert Feature

describe('TDD Assertions', () => {
  it('use assert.* ', () => {
    assert.isOk('everything', 'everything is ok')
    assert.isNotOk(false, 'this will pass')
    assert.equal(3, 3, 'vals equal')
    assert.notEqual(3, 4, 'vals not equal')
    assert.strictEqual(true, true, 'bools strict eq')
    assert.notStrictEqual(5, '5', 'not strict eq')
    assert.deepEqual({ id: '1' }, { id: '1' })
    assert.notDeepEqual({ id: '1' }, { id: '2' })
    assert.isAbove(6, 1, '6 greater than 1')
    assert.isAtLeast(5, 2, '5 gt or eq to 2')
    assert.isBelow(3, 6, '3 strict lt 6')
    assert.isAtMost(4, 4, '4 lt or eq to 4')
    assert.isTrue(true, 'this val is true')
    assert.isNotTrue('tests are no fun', 'val not true')
    assert.isFalse(false, 'val is false')
    assert.isNotFalse('tests are fun', 'val not false')
    //assert.isNull(err, 'there was no error')
    assert.isNotNull('hello', 'is not null')
    assert.isNaN(NaN, 'NaN is NaN')
    assert.isNotNaN(5, '5 is not NaN')
    assert.exists(5, '5 is not null or undefined')
    assert.notExists(null, 'val is null or undefined')
    assert.isUndefined(undefined, 'val is undefined')
    assert.isDefined('hello', 'val has been defined')
    assert.isFunction(x => x * x, 'val is func')
    assert.isNotFunction(5, 'val not funct')
    assert.isObject({ num: 5 }, 'val is object')
    assert.isNotObject(3, 'val not object')
    assert.isArray(['unit', 'e2e'], 'val is array')
    assert.isNotArray('e2e', 'val not array')
    assert.isString('e2e', 'val is string')
    assert.isNotString(2, 'val not string')
    assert.isNumber(2, 'val is number')
    assert.isNotNumber('e2e', 'val not number')
    //assert.isFinite('e2e', 'val is finite')
    assert.isBoolean(true, 'val is bool')
    assert.isNotBoolean('true', 'val not bool')
    assert.typeOf('e2e', 'string', 'val is string')
    assert.notTypeOf('e2e', 'number', 'val not number')

  })
})
Assertion Example
.isOk(object, [message]) assert.isOk('everything', 'everything is ok')
.isNotOk(object, [message]) assert.isNotOk(false, 'this will pass')
.equal(actual, expected, [message]) assert.equal(3, 3, 'vals equal')
.notEqual(actual, expected, [message]) assert.notEqual(3, 4, 'vals not equal')
.strictEqual(actual, expected, [message]) assert.strictEqual(true, true, 'bools strict eq')
.notStrictEqual(actual, expected, [message]) assert.notStrictEqual(5, '5', 'not strict eq')
.deepEqual(actual, expected, [message]) assert.deepEqual({ id: '1' }, { id: '1' })
.notDeepEqual(actual, expected, [message]) assert.notDeepEqual({ id: '1' }, { id: '2' })
.isAbove(valueToCheck, valueToBeAbove, [message]) assert.isAbove(6, 1, '6 greater than 1')
.isAtLeast(valueToCheck, valueToBeAtLeast, [message]) assert.isAtLeast(5, 2, '5 gt or eq to 2')
.isBelow(valueToCheck, valueToBeBelow, [message]) assert.isBelow(3, 6, '3 strict lt 6')
.isAtMost(valueToCheck, valueToBeAtMost, [message]) assert.isAtMost(4, 4, '4 lt or eq to 4')
.isTrue(value, [message]) assert.isTrue(true, 'this val is true')
.isNotTrue(value, [message]) assert.isNotTrue('tests are no fun', 'val not true')
.isFalse(value, [message]) assert.isFalse(false, 'val is false')
.isNotFalse(value, [message]) assert.isNotFalse('tests are fun', 'val not false')
.isNull(value, [message]) assert.isNull(err, 'there was no error')
.isNotNull(value, [message]) assert.isNotNull('hello', 'is not null')
.isNaN(value, [message]) assert.isNaN(NaN, 'NaN is NaN')
.isNotNaN(value, [message]) assert.isNotNaN(5, '5 is not NaN')
.exists(value, [message]) assert.exists(5, '5 is not null or undefined')
.notExists(value, [message]) assert.notExists(null, 'val is null or undefined')
.isUndefined(value, [message]) assert.isUndefined(undefined, 'val is undefined')
.isDefined(value, [message]) assert.isDefined('hello', 'val has been defined')
.isFunction(value, [message]) assert.isFunction(x => x * x, 'val is func')
.isNotFunction(value, [message]) assert.isNotFunction(5, 'val not funct')
.isObject(value, [message]) assert.isObject({num: 5}, 'val is object')
.isNotObject(value, [message]) assert.isNotObject(3, 'val not object')
.isArray(value, [message]) assert.isArray(['unit', 'e2e'], 'val is array')
.isNotArray(value, [message]) assert.isNotArray('e2e', 'val not array')
.isString(value, [message]) assert.isString('e2e', 'val is string')
.isNotString(value, [message]) assert.isNotString(2, 'val not string')
.isNumber(value, [message]) assert.isNumber(2, 'val is number')
.isNotNumber(value, [message]) assert.isNotNumber('e2e', 'val not number')
.isFinite(value, [message]) assert.isFinite('e2e', 'val is finite')
.isBoolean(value, [message]) assert.isBoolean(true, 'val is bool')
.isNotBoolean(value, [message]) assert.isNotBoolean('true', 'val not bool')
.typeOf(value, name, [message]) assert.typeOf('e2e', 'string', 'val is string')
.notTypeOf(value, name, [message]) assert.notTypeOf('e2e', 'number', 'val not number')

Chai-jQuery

การ assert ส่วนหน้า UI (DOM object) โดยจะใช้คำสั่งการ assert เหล่านี้ หลังจากเรียก command cy.get() หรือ cy.contains()

Cypress.config('baseUrl','https://cypress-testing-143fd.web.app/')

describe('Chai-jQuery', () => {
  it('expect DOM Element ', () => {
    cy.visit('/elements.html')
      .get('form.ui.form').should($el => {
        expect($el).to.have.attr('class', 'ui form')

        const $notDisabled = $el.find('#text')
        expect($notDisabled).to.have.prop('disabled', false)

        expect($el).to.have.css('background-color', 'rgb(255, 255, 200)')
        expect($el).to.have.data('random', '77547562649602259494')
        expect($el).to.have.class('ui')

        const $email = $el.find('[name="email"]')
        expect($email).to.have.id('email')

        const $html = $el.find('#i-love-test-html')
        expect($html).to.have.html('\n              <h1>I love testing</h1>\n            ')

        const $text = $el.find('#i-love-test-text')
        expect($text).to.have.text('\n              I love testing\n            ')
        expect($text).to.contain('I love testing')

        expect($email).to.have.value('[email protected]')

        expect($email).to.be.visible

        const $hidden = $el.find('[name="hidden"]')
        expect($hidden).to.be.hidden

        const $option0 = $el.find('#select option:eq(1)')
        expect($option0).not.to.be.selected

        const $options = $el.find('#select option')
        expect($options).to.have.lengthOf(5)
        
        cy.log('lengthOf------------')
        const $mazda = $el.find('[name="mazda-checked"]')
        expect($mazda).not.to.be.checked

        const $focus = $el.find('#text')
        //expect($focus).not.to.be.focused
        //expect($focus).to.have.focus

        expect($focus).to.be.enabled

        const $disabled = $el.find('[name="text-disabled"]')
        expect($disabled).to.be.disabled

        //expect($focus).not.to.be.empty
        //expect($nonexistent).not.to.exist
        //expect($emptyEl).to.match(':empty')
        //expect($el).to.contain('text')
        //expect($el).to.have.descendants('div')
      })


  })
})
https://cypress-testing-143fd.web.app/elements.html
Chainers Assertion
attr(name, [value]) expect($el).to.have.attr('foo', 'bar')
prop(name, [value]) expect($el).to.have.prop('disabled', false)
css(name, [value]) expect($el).to.have.css('background-color', 'rgb(0, 0, 0)')
data(name, [value]) expect($el).to.have.data('foo', 'bar')
class(className) expect($el).to.have.class('foo')
id(id) expect($el).to.have.id('foo')
html(html) expect($el).to.have.html('I love testing')
text(text) expect($el).to.have.text('I love testing')
value(value) expect($el).to.have.value('[email protected]')
visible expect($el).to.be.visible
hidden expect($el).to.be.hidden
selected expect($option).not.to.be.selected
checked expect($input).not.to.be.checked
focus[ed] expect($input).not.to.be.focused
expect($input).to.have.focus
enabled expect($input).to.be.enabled
disabled expect($input).to.be.disabled
empty expect($el).not.to.be.empty
exist expect($nonexistent).not.to.exist
match(selector) expect($emptyEl).to.match(':empty')
contain(text) expect($el).to.contain('text')
descendants(selector) expect($el).to.have.descendants('div')

Sinon-Chai

การ assert behavior ของ website event ภายใน ต่าง ๆ ทั้ง window object function (alert ,confirm ,prompt) หรือ custom js function ต่าง ๆ จะใช้ assert spy , stub ตัวอย่างที่สามารถ assert ได้ เช่น มีการ call spy/stub หรือไม่ call กี่ครั้ง จะสามารถเช็คได้ด้วย assert ชุดนี้

Cypress.config('baseUrl','https://cypress-testing-143fd.web.app/')
describe('Sinon-Chai', () => {
  it('use sinon expect spy&tub', () => {
    let stubFirst ,stubSecound ,stubThird,stubFour,stubFive;
    let countStubFiveClick = 0;
    cy.visit('/window.html',{
      onLoad(win){
        stubFirst = cy.stub(win,'mySpyCalledFirst',()=>{})
        stubSecound = cy.stub(win,'mySpyCalledSecound',()=>{})
        stubThird = cy.stub(win,'mySpyCalledThird',()=>{})
        stubFour = cy.stub(win,'mySpyCalledFour',()=>{
          return 'mySpyCalledFour click 999'
        })
        stubFive = cy.stub(win,'mySpyCalledFive',()=> countStubFiveClick+= 2)
      }
    })
    .get('#btnFirst').click()
    .get('#btnSecound').click()
    .get('#btnSecound').click()
    .get('#btnThird').click()
    .get('#btnThird').click()
    .get('#btnThird').click()
    .get('#btnFour').click()
    .get('#btnFive').click().get('#btnFive').click()
    //.wait(2000)
    .then(()=>{
      expect(stubFirst).to.be.called
      expect(stubFirst).to.have.callCount(1)
      expect(stubFirst).to.be.calledOnce
      expect(stubSecound).to.be.calledTwice
      expect(stubThird).to.be.calledThrice
      expect(stubFirst).to.be.calledBefore(stubSecound)
      expect(stubThird).to.be.calledAfter(stubSecound)
      expect(stubFour).to.have.returned('mySpyCalledFour click 999')
      expect(stubFive).to.have.returned(countStubFiveClick)
      expect(stubFirst).to.have.always.returned(undefined)
      /*expect(stubFirst).to.be.calledWithNew
      expect(stubFirst).to.always.be.calledWithNew
      expect(spy).to.be.calledOn(context)
      expect(spy).to.always.be.calledOn(context)
      expect(spy).to.be.calledWith(...args)
      expect(spy).to.always.be.calledWith(...args)
      expect(spy).to.be.calledWithExactly(...args)
      expect(spy).to.always.be.calledWithExactly(...args)
      expect(spy).to.be.calledWithMatch(...args)
      expect(spy).to.always.be.calledWithMatch(...args)
      
      expect(spy).to.have.thrown(errorObjOrErrorTypeStringOrNothing)
      expect(spy).to.have.always.thrown(errorObjOrErrorTypeStringOrNothing)*/
    })
    

  })
})
Sinon.JS property/method Assertion
called expect(spy).to.be.called
callCount expect(spy).to.have.callCount(n)
calledOnce expect(spy).to.be.calledOnce
calledTwice expect(spy).to.be.calledTwice
calledThrice expect(spy).to.be.calledThrice
calledBefore expect(spy1).to.be.calledBefore(spy2)
calledAfter expect(spy1).to.be.calledAfter(spy2)
calledWithNew expect(spy).to.be.calledWithNew
alwaysCalledWithNew expect(spy).to.always.be.calledWithNew
calledOn expect(spy).to.be.calledOn(context)
alwaysCalledOn expect(spy).to.always.be.calledOn(context)
calledWith expect(spy).to.be.calledWith(...args)
alwaysCalledWith expect(spy).to.always.be.calledWith(...args)
calledWithExactly expect(spy).to.be.calledWithExactly(...args)
alwaysCalledWithExactly expect(spy).to.always.be.calledWithExactly(...args)
calledWithMatch expect(spy).to.be.calledWithMatch(...args)
alwaysCalledWithMatch expect(spy).to.always.be.calledWithMatch(...args)
returned expect(spy).to.have.returned(returnVal)
alwaysReturned expect(spy).to.have.always.returned(returnVal)
threw expect(spy).to.have.thrown(errorObjOrErrorTypeStringOrNothing)
alwaysThrew expect(spy).to.have.always.thrown(errorObjOrErrorTypeStringOrNothing)

คำสั่ง asserts เหล่านี้ต้องใช้การใช้งาน บ่อย ๆ ถึงจะจำการเรียกใช้งานได้ บทความ ถัดไปจะมาเล่าถึงการใช้งาน spy / stub คอยติดตามด้วยนะครับ

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *