راه اندازی تست روی reactJs و redux با استفاده از jest

راه اندازی تست روی reactJs و redux  با استفاده از jest نوشته شده در   react 9 دسامبر , 2018 توسط  عباس حسینی

در اینم مقاله قصد دارم نحوه راه اندازی محیط unit test برای reactJs و redux از طریق jest را توضیح دهم و قبل از شروع کار بهتر است توضیح مختصری در مورد shallow rendering بدهم

shallow rendering روشیست که به ما کمک میکند به رفتار یک کامپوننت بعد از رندر، بدون نگرانی در مورد کامپوننت های فرزندانش در قالب یک آبجکت دسترسی داشته باشیم

reactJs یک کتابخانه به نام react-test-renderer برای این کار دارد اما از آنجاییکه کتابخانه react-test-renderer مشکلاتی چون عدم پشتیبانی از refs دارد، مستند رسمی reactJs استفاده از کتابخانه Enzyme را به ما پیشنهاد داده است

نصب و راه اندازی محیط تست

در این آموزش من از reactJs نسخه ۱۶ استفاده میکنم و برای شروع کتابخانه های زیر را نیاز داریم

  1. Enzyme : کتابخانه که در shallow rendering و همچنین  بررسی و دستکاری خروجی کامپوننت های reactJs به ما کمک میکند
  2. Jest : کتابخانه تست مورد استفاده ما در این آموزش است که فیسبوک هم از آن برای تست بهره گرفته است
  3. enzyme-adapter-react-16 : کتابخانه سازگار کننده reactJS که متناسب با ورژن مورد استفاده باید نصب شود که در اینجا ورژن مورد استفاده من ۱۶ میباشد
  4. enzyme-to-json : برای تبدیل شی ساخته شده به یک JSON مورد استفاده قرار میگیرد
  5. redux-mock-store : کتابخانه ای که برای تست redux و mock کردن store مورد استفاده قرار میگیرد

قبل از از شروع نصب در مسیر روت پروژه پوشه ای به نام __tests__ ساخته و طبق نقشه زیر پوشه های زیر را درون آن بسازید، دقت کنید که کتابخانه jest فایل های موجود در این پوشه را به عنوان فایل های تست شناسایی میکند و هنگام اجرای تست تمام فایل های موجود در آن را مورد بررسی قرار میدهد


├── __tests__
│   ├── actions
│   │   └── ..
│   ├── components
│   │   └── ...
│   ├── reducers
│   │   └── ...
│   └── setup
│       └── setupEnzyme.js

کتابخانه jest  هنگام اجرای تست به سه روش فایل های تست را شناسایی میکند

  1. فایل هایی که داخل پوشه __tets__ قرار دارند
  2. فایل هایی که پیشوند test در نامشان دارند – list.test.js
  3. فایل هایی که پیشوند spec در نامشان دارند – list.spec.js

پس از مشخص شدن مسیری که فایل های تست را شامل میشود به نصب نیازمندی های تست میپردازیم.

برای نصب jest از دستور زیر استفاده میکنیم


npm i --save--dev jest

بعد از jest با استفاده از کد زیر enzyme-adapter-react-16 ، enzyme-to-json و enzyme را بطور یکجا نصب کنید


npm i --save--dev enzyme enzyme-adapter-react-16 enzyme-to-json

حال که کتابخانه enzyme نصب شده نیاز به پیکربندی برای سازگاری با jest دارد ، دقت کنید که jest به صورت پیش فرض پوشه __tests__ را به عنوان پوشه ای که لیست تست ها در آن قرار دارد، شناسایی میکند.

بنابراین یک فایل به نام enzymeConfig.js در مسیر __tests__/enzyme میسازیم و کد های زیر را داخل آن قرار میدهیم


import enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

enzyme.configure({ adapter: new Adapter() });

سپس سراغ فایل packages.json واقع در روت پروژه رفته تا enzymeConfig.js ساخته شده را به jest معرفی کنیم برای این کار کد زیر را به آن اضافه میکنیم

setupTestFrameworkScriptFile برای بارگزاری فایل های کانفیک enzyme است و از این طریق enzymeConfig.js را به عنوان کانفیک به آن معرفی میکنیم

testPathIgnorePatterns لیستی از مسیر هاییست که میخواهیم jest هنگام اجرای تست، آنها را نادیده بگیرد

در انتها در لیست اسکریپت های packages.json کد های زیر را قرار میدهیم تا پس از این از طریق دو دستور test و test-watch بتوانیم تست را فراخوانی کنیم


"scripts": {
  "test": "jest --colors --coverage",
  "test-watch": "jest --colors --coverage --watch "
},

رفع یک مشکل در اجرای jest

اگر در گذشته یا هنگام مطالعه این آموزش از jest استفاده کردید احتمال دارد که با خطای زیر مواجه شوید


Test suite failed to run

    Jest encountered an unexpected token

    This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.

    By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

    Here's what you can do:
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://facebook.github.io/jest/docs/en/configuration.html
{"Object.":function(module,exports,require,__dirname,__filename,global,jest)

علت به وجود آمدن این مشکل عدم پیکر بندی jest برای پشتیبانی از babel میباشد
برای حل این مشکل ابتدا دستور زیر را اجرا کنید تا یکسری پکیج های مربوط به babel نصب شود


npm i --save-dev babel-core babel-loader babel-jest babel-preset-env babel-preset-react jest-cli react-addons-test-utils

سپس یک فایل به نام .babelrc بسازید و کد های زیر را داخل آن قرار دهید و در انتها این فایل را در روت پروژه قرار دهید با این کار مشکل شما حل خواهد شد


{
    "presets": ["env", "react"]
}

اگر درساخت فایل .babelrc به مشکل خوردید میتوانید از لینک زیر دانلود کنید
دانلود babelrc

نحوه تست کامپوننت 

برای تست یک کامپوننت یک فایل هم نام آن داخل پوشه  __tests__ میسازیم، سپس علاوه بر فایل اصلی کتابخانه های مربوط به تست را بارگزاری میکنیم و به شکل زیر عمل میکنیم در اینجا من یک تست ساده برای کامپوننت List نوشتم


import React from 'react';
import { shallow, mount, render } from 'enzyme';

import toJson from 'enzyme-to-json';
import configureStore from 'redux-mock-store'; // Smart components
import List from '../../../src/components/list/list';

//test case
test('Test component', () => {
    //در ابتدار سه عنصر به لیست پاس میدهیم
    const list = [
        {value:'item 1'},
        {value:'item 2'},
        {value:'item 3'},
    ]
    const shallowDom = shallow();

    //ابتدا سه تا بوده الان باید چهار تا باشد
    shallowDom.props().addItem('abbas');
    expect(shallowDom.find('li').length).toBe(4)
 
});

در صورت عدم آشنایی به نحوه نوشتن تست با enzyme و jest لینک های ذکر شده در زیر را مطالعه کنید

نحوه تست reducer ها

برای تست کردن یک reducer ابتدا یک فایل هم نام reducer در مسیر __tests__/reducers میسازیم و از آنجاییکه reducer یک تابع است با دو ورودی یکی state و دیگری action برای تست آن کافیست یک action  به آن پاس بدهیم و ببینیم که خروجی state بر اساس آن تغییری میکند یا خیر


// Actions to be tested
import addItem from './../../src/acions/addItem'
// Reducer to be tested
import listReducer from './../../src/reducers/listReducer';

test ('test addItem action', function(){
  
    const action = {
        type :'ADD_ITEM',
        columns : 'test item'
    };

    describe('testing functionality', function(){
       expect(store.getActions()).toMatchSnapshot(gridReduce(undefined, action))
    })

})

در این مثال مقدار state را undefined و مقدار action را نیز در بالا تعریف و برای listReducer ارسال شده است اگر خروجی اجرای آن بر اساس مقدار جدید action باشد یعنی reducer بدرستی کار میکند و در غیر اینصورت یعنی ایراد دارد و باید بررسی شود و این خروجی هم هنگام تست و هم داخل فایل snapshot قابل مشاهده است

نحوه تست action ها

برای تست action ها ابتدا باید از طریق کتابخانه redux-mock-store بتوانیم store را mock کرده و سپس action مورد نظر را روی store ماک شده dispath میکنیم تا درستی آن را از طریق متد getActions() کنیم


import configureStore from 'redux-mock-store';
const mockStore = configureStore();
const store = mockStore();

// Actions to be tested
import addItem from './../../src/acions/addItem'

test ('test addItem action', function(){
    store.dispatch(updateColumns('test item'))
    
    describe('testing functionality', function(){
        expect(store.getActions()).toMatchSnapshot()
    })
})

در این مثال اکشن addItem داخل store ماک شده dispatch شده و خروجی متد getActions را snapshot کردم تا خروجی آن مورد بررسی قرار گیرد