【问题标题】:How to test component using spyFn in React Jest如何在 React Jest 中使用 spyFn 测试组件
【发布时间】:2021-08-13 10:09:25
【问题描述】:

我想在将动作传递给子项时测试我的组件动作。简而言之,像 twitter 或 facebook 这样的每个来源都有自己的一组操作。我想检查它是否被调用或不使用间谍。

这是我的动作组件

const targetMetric = 'account dropdown'

const availableActions = {
    addQuery: {
        facebook: '^facebook://page/',
        twitter: true
    },
    exclude: {
        blog: [
            '^blog://user/eventregistry/',
            '^eventregistry://user/'
        ],
        news: [
            '^news://user/eventregistry/',
            '^eventregistry://user/'
        ],
        twitter: true,
        youtube: true
    },
    reportAsNews: {
        youtube: true,
        mastodon: true,
        twitter: true
    }
}

const requiredHandlers = {
    exclude: [
        'onExcludeProfile'
    ],
    reportAsNews: [
        'onReportAsNews'
    ]
}
class Actions extends React.PureComponent {

    get actions() {
        const { account, handlers } = this.props
        const actions = {};

        Object.keys(availableActions).forEach(key =>
            actions[ key ] = false
        )

        Object.keys(actions).forEach(key => {
            const value = (
                !!account.uri
                &&
                availableActions[ key ][ account.type ]
            )

            if (!value) {
                return
            }

            if (typeof value === 'boolean') {
                actions[ key ] = value
                return
            }

            if (typeof value === 'string') {
                const re = new RegExp(value, 'i')
                actions[ key ] = re.test(account.uri)

                return
            }

            if (
                typeof value === 'object'
                &&
                Array.isArray(value)
            ) {
                actions[ key ] = value.some(v => {
                    const re = new RegExp(v, 'i')
                    return re.test(account.uri)
                })
            }
        })

        Object.keys(actions).forEach(key => {
            if (!actions[ key ] || !requiredHandlers[ key ]) {
                return
            }

            actions[ key ] = requiredHandlers[ key ].some(i => handlers[ i ])
        })

        if (actions.addQuery) {
            actions.addQuery = Object
                .keys(this.addQueryActions)
                .some(key => this.addQueryActions[ key ])
        }

        return actions
    }

    get addQueryActions() {
        const { availableLanguages = [], userIsAdmin } = this.context
        const { caseItem, handlers } = this.props

        const actions = {
            addQueryToFilter: !caseItem.isPaused && !!handlers.onAddQuery,
            addQueryToAccountList: userIsAdmin && !!handlers.onAddToAccountList
        }

        actions.addQueryToSearch = actions.addQueryToFilter && !!availableLanguages.length

        return actions
    }

    get actor() {
        return pick(
            this.props.account,
            [ 'uri', 'name', 'link' ]
        )
    }

    onExclude = () => {
        const { account, handlers, isCaseLocked } = this.props

        if (isCaseLocked) {
            return
        }

        handlers.onExcludeProfile(account)
    }

    onReportAsNews = () => this.props.handlers.onReportAsNews(this.actor)

    onAddToAccountList = () => {
        const { account, from, handlers } = this.props

        handlers.onAddToAccountList(account, from)
    }

    onAddToQuery = type => ({ language } = {}) => {
        const { account, caseItem, handlers } = this.props
        const { id } = caseItem
        const metrics = {
            index: getId(),
            language,
            type
        }

        handlers.onAddQuery({
            ...metrics,
            expression: this.expressionFromAccount(account),
            hideSearch: true,
            id,
        })

        return metrics
    }

    expressionFromAccount = account => ({
        and: [
            { account }
        ]
    })

    trackExcludeEvent = () => {
        const { account } = this.props

        this.trackEvent(
            events.excludeAccounts,
            {
                accountsAdded: 1,
                source: account.type
            }
        )
    }

    trackCreateNewQueryEvent = ({ index, language, type }) => {
        const eventNameMap = {
            filters: events.createNewFilter,
            queries: events.createNewSearch,
        }

        const metrics = {
            queryId: index,
            target: targetMetric
        }

        if (type === 'queries') {
            metrics[ 'language' ] = language.toLowerCase()
        }

        this.trackEvent(
            eventNameMap[ type ],
            metrics
        )
    }

    trackReportAsNewsEvent = () => (
        this.trackEvent(
            events.reportAsNews,
            { source: this.props.account.type }
        )
    )

    trackEvent = (eventName, props = {}) => {
        const { from, message } = this.props

        eventTracker.track(
            eventName,
            {
                ...props,
                from,
                messageUri: message.uri
            }
        )
    }

    getLangMenuActions = ({ handlers, isCaseLocked }, { availableLanguages }) => {
        if (
            isCaseLocked
            ||
            !availableLanguages
            ||
            !handlers.onAddQuery
        ) {
            return []
        }

        const onClick = compose(
            this.trackCreateNewQueryEvent,
            this.onAddToQuery('queries')
        )

        return availableLanguages.map(({ label, value: language }) => ({
            handler: onClick.bind(this, { language }),
            id: `add-account-to-search-lang-${language}`,
            label
        }))
    }

    getActions = () => {
        const { isCaseLocked } = this.props
        const { userIsAdmin } = this.context

        const actions = []

        if (this.actions.addQuery) {
            const addQueryAction = {
                id: 'add-account-to',
                isInactive: isCaseLocked && !userIsAdmin,
                label: i18n.t('SOURCES.DROPDOWN_ADD_TO'),
                children: []
            }

            if (this.addQueryActions.addQueryToSearch) {
                addQueryAction.children.push({
                    id: 'add-account-to-search',
                    isInactive: isCaseLocked,
                    label: i18n.t('SOURCES.DROPDOWN_NEW_SEARCH'),
                    children: this.getLangMenuActions(this.props, this.context)
                })
            }

            if (this.addQueryActions.addQueryToFilter) {
                addQueryAction.children.push({
                    handler: compose(
                        this.trackCreateNewQueryEvent,
                        this.onAddToQuery('filters')
                    ),
                    id: 'add-account-to-filter',
                    isInactive: isCaseLocked,
                    label: i18n.t('SOURCES.DROPDOWN_NEW_FILTER')
                })
            }

            if (this.addQueryActions.addQueryToAccountList) {
                addQueryAction.children.push({
                    handler: this.onAddToAccountList,
                    id: 'add-account-to-account-list',
                    label: i18n.t('SOURCES.DROPDOWN_ACCOUNT_LIST')
                })
            }

            actions.push(addQueryAction)
        }

        if (this.actions.reportAsNews) {
            actions.push({
                handler: compose(
                    this.onReportAsNews,
                    this.trackReportAsNewsEvent
                ),
                id: 'report-as-news',
                label: i18n.t('SOURCES.DROPDOWN_REPORT_AS_NEWS')
            })
        }

        if (this.actions.exclude) {
            actions.push({
                handler: compose(
                    this.onExclude,
                    this.trackExcludeEvent
                ),
                id: 'exclude-account',
                isInactive: isCaseLocked,
                label: i18n.t('SOURCES.DROPDOWN_EXCLUDE')
            })
        }

        console.log(actions)

        return actions
    }

    render() {
        return this.props.children({
            actions: this.getActions()
        })
    }
}

这是我的测试文件

import expect from 'expect'

const injectActions = require('inject-loader!./actions')

const Actions = injectActions({
    'cm/common/event-tracker': {
        eventTracker: {
            track: () => {},
            clear: () => {}
        },
        events: {
            createNewFilter: '...',
            createNewSearch: '...',
            excludeAccounts: '...',
            reportAsNews: '...',
        }
    },
}).default

const handlers = {
    onAddQuery: () => { },
    onAddToAccountList: () => { },
    onExcludeProfile: () => { },
    onReportAsNews: () => { }
}

const testProps = {
    twitter: {
        account: {
            name: 'Twitter account',
            uri: 'twitter://status/12345',
            type: 'twitter'
        },
        handlers,
    },
    facebookPage: {
        account: {
            name: 'Facebook page account',
            uri: 'facebook://page/12345',
            type: 'facebook'
        },
        handlers
    }
}

describe('Actions component', () => {
    let node

    beforeEach(() => {
        node = document.createElement('div')
    })

    afterEach(() => {
        ReactDOM.unmountComponentAtNode(node)
    })

    it('returns empty actions array by default', () => {
        const spyFn = expect.createSpy().andReturn(null)

        ReactDOM.render(
            <Actions>{spyFn}</Actions>,
            node
        )

        expect(spyFn).toHaveBeenCalledWith({ actions: [] })
    })


    describe('Twitter', () => {
        it('returns "Exclude" action', () => {
            const { account, handlers } = testProps.twitter
            const spyFn = expect.createSpy()

            ReactDOM.render(
                <Actions
                    account={account}
                    handlers={{
                        onExcludeProfile: handlers.onExcludeProfile
                    }}
                    isCaseLocked={false}
                >
                    {spyFn}
                </Actions>,
                node
            )

            expect(spyFn).toHaveBeenCalledWith({ actions: [
                {
                    handler: () => {},
                    id: 'exclude-account',
                    isInactive: false,
                    label: 'Exclude',
                }
            ] })
        })
})

第一个单元格工作正常,但第二个是错误的。其实我也不需要那里的所有对象。我只想确定它在那里包含id: 'exclude-account'

请大家帮忙。

【问题讨论】:

    标签: reactjs unit-testing jestjs


    【解决方案1】:

    您可以使用expect.objectContaining(object)

    匹配任何递归匹配预期属性的接收对象

    例如

    describe('68770432', () => {
      test('should pass', () => {
        const spyFn = jest.fn();
        spyFn({
          actions: [{ handler: () => {}, id: 'exclude-account', isInactive: false, label: 'Exclude' }],
        });
        expect(spyFn).toBeCalledWith({
          actions: [
            expect.objectContaining({
              id: 'exclude-account',
            }),
          ],
        });
      });
    });
    

    【讨论】:

      猜你喜欢
      • 2018-12-16
      • 1970-01-01
      • 2017-04-13
      • 2021-12-06
      • 2017-02-12
      • 1970-01-01
      • 2021-12-10
      • 2018-08-20
      • 2018-02-15
      相关资源
      最近更新 更多