use-between icon indicating copy to clipboard operation
use-between copied to clipboard

Jest Testing with use-between hooks

Open melloware opened this issue 3 years ago • 11 comments

Anyone Jest testing with use-between hooks? Let me give you a shortened example code...

export const MyDialog = () => {

   // shared use between hook set in another component
   const { displayAddAnalysis, setDisplayAddAnalysis } = useMyHook();

    return (
        <Dialog id="dlg-new-planned-analysis" 
                      header={`New Planned Analysis`} 
                      visible={displayAddAnalysis} 
                       onHide={onHide}>
    )
}

I need to set the displayAddAnalysis use-between hook value to true for my Jest test.

Here is the only way we could figure out to set it because use-between must be used in a Component is to create a fake <testComponent> but some on our dev team think this feels wrong. Any thoughts on the best way to test use-between hooks in Jest?

function getDialog() {
    //Test component exists so that the setMyHook can be switched to true.
    //Otherwise, the Dialog component will not exist to test
    function TestComponent() {
        const { setDisplayAddAnalysis } = usePlannedAnalysisHook();
        setDisplayAddAnalysis(true);
        return null;
    }
    render(<TestComponent />);
    return render(<MyDialog />);
}

test("Making sure error messages do not show on component loading", async () => {
    //Arrange
    const Dialog = getDialog();

    // Act
    const study = await Dialog.findByTestId("studyError");

    //Assert
    expect(study.textContent).toMatch("");
});

}

melloware avatar Jan 26 '22 18:01 melloware

Thanks for using the library!

I made API for testing, welcome to try it!

// ./shared-counter.test.js
import { clear, get, act, on } from 'use-between'
import { useCounter } from './shared-counter'


// Clean up after each test if necessary
afterEach(() => clear())


// Test example
it('It works', async () => {
  expect(get(useCounter).count).toBe(0)

  // It's good practice to use "act" for modifying operations
  await act(() => {
    get(useCounter).inc()
  })
  expect(get(useCounter).count).toBe(1)

  await act(() => {
    get(useCounter).inc()
  })
  expect(get(useCounter).count).toBe(2)
})


it('It works with spy', async () => {
  const spy = jest.fn()

  // Subscribe to a state change
  on(useCounter, (state) => spy(state.count))

  await act(() => {
    get(useCounter).inc()
  })
  expect(spy).toBeCalledWith(1)

  await act(() => {
    get(useCounter).dec()
  })
  expect(spy).toHaveBeenLastCalledWith(0)
})

github.com/betula/use-between/blob/master/tests/example.test.ts

// ./shared-counter.js
import { useState, useCallback } from 'react'

export const useCounter = () => {
  const [count, setCount] = useState(0)
  const inc = useCallback(() => setCount((c) => c + 1), [])
  const dec = useCallback(() => setCount((c) => c - 1), [])
  return {
    count,
    inc,
    dec
  }
}

Happy Coding!

betula avatar Jan 30 '22 10:01 betula

very cool! This is a welcome addition for people Jest testing.

melloware avatar Jan 30 '22 15:01 melloware

Will this be in the 1.0.2 release?

melloware avatar Jan 30 '22 15:01 melloware

Thanks! Implemented in today's release 1.1.0 You can try it out.

betula avatar Jan 30 '22 15:01 betula

I will give a shot tomorrow at the office. Thanks for the quick turnaround! I will let you know how it goes.

melloware avatar Jan 30 '22 15:01 melloware

Hey I just began using the updated API for Jest testing, and everything is working as expected, minus Jest complaining in the logs.

 Warning: An update to PlannedAnalysisDialog inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */
    
    This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
        at PlannedAnalysisDialog (...PlannedAnalysisDialog.tsx:33:120) 

A sample test in the suite:

it("A valid type should leave the error message blank upon submit", async () => {
    await act(() => {get(usePlannedAnalysisHook).setDisplayAddAnalysis(true)})
    const dialog = render(<PlannedAnalysisDialog />);
    const typeF = dialog.getAllByText("Select a type");
    const button = await dialog.findByTestId("submitButton");

    fireEvent.change(typeF[0], { target: { value: { name: "Integrated", code: "Integrated" } } });
    act(() => {button.click();})

    const studyE = await dialog.findByTestId("typeError");
    expect(studyE.textContent===null);
    });

The log error doesn't point to anything in the test directly though, it points to the usage of the use-between hook in the component being tested.

PlannedAnalysisDIalog.test.zip

Abielf avatar Feb 03 '22 15:02 Abielf

@Abielf can you zip your test.ts file and then attach it to your ticket so he can see your full test?

melloware avatar Feb 03 '22 15:02 melloware

Hello @Abielf!

Okay. With the "act" questions arose, I removed it from "use-between" (1.2.1).

Now we need to use "act" from "@testing-library/react" only where we need it. The "act" is exactly what is needed in those places where the warnings "was not wrapped in act(...)" appear.

There is no additional "act" for "use-between" operations anymore.

import { get, useBetween, clear, mock } from 'use-between'
import { act, render } from '@testing-library/react'
afterEach(clear)
it('It works', async () => {
  expect(get(useCounter).count).toBe(0)

  // Without act
  get(useCounter).inc()

  // Check result
  expect(get(useCounter).count).toBe(1)

  get(useCounter).inc()
  expect(get(useCounter).count).toBe(2)
})
it('It works with testing-library render component', async () => {
  const Counter = () => {
    const { count } = useBetween(useCounter)
    return <i data-testid="count">{count}</i>
  }

  const el = render(<Counter />)
  expect((await el.findByTestId('count')).textContent).toBe('0')

  // You should use "act" from @testing-library/react
  // otherwise there will be a warning.
  act(() => {
    get(useCounter).dec()
  })
  expect((await el.findByTestId('count')).textContent).toBe('-1')
})

I also have great news! I made a "mock" function for you!

it('It works with testing-library render component with mock', async () => {
  mock(useCounter, { count: 10 })

  const el = render(<Counter />)
  expect((await el.findByTestId('count')).textContent).toBe('10')

  // You should use "act" from @testing-library/react
  // otherwise there will be a warning.
  // Because here we are updating the state of the mock.
  act(() => {
    mock(useCounter, { count: 15 })
  })
  expect((await el.findByTestId('count')).textContent).toBe('15')
})

Try It Out and Happy Coding!

betula avatar Feb 05 '22 09:02 betula

You rock @betula we will let you know how it goes.

melloware avatar Feb 05 '22 12:02 melloware

@betula We confirmed these changes now make it all work perfectly. You are awesome!

melloware avatar Feb 07 '22 16:02 melloware

Many thanks! It was great teamwork with you!😊👍

betula avatar Feb 08 '22 06:02 betula