Wimal Weerawansa
Wimal Weerawansa

Reputation: 157

React, TypeScript &RxJS testing with Jest not working

I have a simple button component in React TypeScript, I using fromEvent method to translate click event to Observable wrapper of RxJs it is defiened fromEvent(el:HTMLElement, eventType:string) in RxJS

type NullableObservarbel = Observable<any> | null
type NUllabe = HTMLElement | null

    export const makeObservable = (el:NUllabe, eventType:string):NullableObservarbel => el ? fromEvent(el, eventType) : null;
type Props = {
    interval?: number // timee interval in millisecond
    label?:string
}

const Button:SFC<Props> = ({label}:Props) => {
    const btn = useRef(null)
    useEffect(() => {
        if(btn.current !== null){
            const observable = makeObservable(btn.current, 'click')
        }
    }, [])
    return <button ref={btn}>{label}</button>
}

As you can see makeObservable method,

  1. I want to test this method with Jest --> if I pass proper HTMLElement and string as an argument it should return Observable
  2. I want to simulate click event then I want to check `returned observable is working and subscribers of that observable are triggering.

But none of this test is not working as expected.

This is the test first one always pass even their is no el passed, the second one always fails.

import React from 'react'
import { shallow, mount } from 'enzyme'
import Button, {makeObservable} from './Button'
import {Observable} from 'rxjs'

describe('Observable', () => {
    it('should create observable', () => {
        const wrapper = shallow(<Button />)
        const el = wrapper.find('button')
        const observable = makeObservable(el, 'click') // here i have the issue
        expect(observable instanceof Observable).toBe(true)
    })
    it('should create observable', () => {
        const wrapper = shallow(<Button />)
        const el = wrapper.find('button')
        const observable = makeObservable(el, 'click')

        let _six = 0;
        if(observable){
        observable
            .pipe(map(e => 6))
            .subscribe(s => {
                _six = s
            })
        }
        el.simulate('click')
       expect(_six).toEqual(6) // fails always
     })
})

The biggest problem is I can't get HTMLElement type from wrapper so i just use find button and pass the result as an element, But

expect(observable instanceof Observable).toBe(true) this line is always passing event if I pass null as argument for el to makeObservable.

Please help me to test properly these scenarios.

Upvotes: 1

Views: 1228

Answers (1)

Kamaal ABOOTHALIB
Kamaal ABOOTHALIB

Reputation: 3548

You can try with testing-library/react instead of using enzyme shallow, I have added data-testid="btn" attribute to your component then from the test I use testing-library/react to render your button then get your HTMLElement of your button I used this testid attribute.

It worked as expected in test and browser, pls check the source code.

Button.tsx

import React, { SFC, useRef, useEffect, useState, RefObject} from 'react'
import {fromEvent, Observable} from 'rxjs'
import {map, debounceTime} from 'rxjs/operators'

type NullableObservarbel = Observable<any> | null;
type NUllabe = HTMLButtonElement | null; // more precise type
export const makeObservable = (el:NUllabe, eventType:string):NullableObservarbel => el ? fromEvent(el, eventType) : null;

type Props = {
    interval?: number // timee interval in millisecond
    label?:string
}

export type Result = [RefObject<HTMLButtonElement>, number]

// decoupled it from Button function body because you can test this later.
export const useEls = ():Result => {
    const btn: RefObject<HTMLButtonElement> = useRef(null)
    const [count, updateCount] = useState<number>(0)
    useEffect(() => {
        const el = btn ? btn.current : null
        if(el){
            updateCount(1)
            let _count = count
            const observerble =  makeObservable(el, 'click');
            if(observerble){
                observerble.pipe(
                    map(e => _count++),
                    //debounceTime(400)
                ).subscribe(c => updateCount(c))
            }
        }
    }, [])
    return [btn, count]
} 

const Button:SFC<Props> = (props:Props) => {
    const [btn, count] = useEls()
    return <button data-testid="btn" ref={btn}>Hello {count}</button>
}


export default Button

Button.test.ts

import React from 'react'
import Button, {makeObservable} from './Button'
import {Observable} from 'rxjs'
import {map, debounceTime} from 'rxjs/operators'
import {render, fireEvent} from '@testing-library/react'

describe('Observable', () => {
    it('should create observable', () => {
        const {getByTestId} = render(<Button/>)
        const el = getByTestId('btn') as HTMLButtonElement
        const observable = makeObservable(el, 'click')
        expect(observable instanceof Observable).toBe(true)
    })

    it('should return false', () => {
        const observable = makeObservable(null, 'click')
        expect(observable instanceof Observable).toBe(false)
    })

    it('Should subscribe observable', (done) => {
        const {getByTestId} = render(<Button/>)
        const el = getByTestId('btn') as HTMLButtonElement
        const observerble =  makeObservable(el, 'click');
        if(observerble){
            let count = 1
            observerble
                .pipe(
                    map(e => count++),
                    debounceTime(400) // delay clicks if you want
                )
                .subscribe(s => {
                    expect(s).toEqual(6)// test just inside the subscription
                    done();
                })
            fireEvent.click(el)
            fireEvent.click(el)
            fireEvent.click(el)
            fireEvent.click(el)
            fireEvent.click(el)
            fireEvent.click(el)
        }
    })
})

This test is running as expected with simulated clicks and delays.


React testing library

Upvotes: 3

Related Questions