jfairbank / redux-saga-test-plan

Test Redux Saga with an easy plan.
http://redux-saga-test-plan.jeremyfairbank.com
MIT License
1.25k stars 127 forks source link

Error on expectSaga.provide selector (state is undefined) #332

Open diegofelipece opened 4 years ago

diegofelipece commented 4 years ago

Hey there! I'm implementing redux-saga-test-plan on an existent project, and I'm having some troubles to use expectSaga.provide with a specific kind of selector, and I do believe that this has been caused by some bug on the provide at the moment of recognizing and mocking my selector.

I have something like that:

// Selector
const selectFruit = name => state => state.fruits[name]

// Saga
function* onGetFruit({ payload: { name } }) {
  const myFruit = yield select(selectFruit(name))

  if (myFruit === 'apple') {
    const apple = {
      edible: true, 
      color: 'red',
    }
    yield put(eatTheFuit(apple))
  }
}

// Test
it('should find an apple and dispatch an eat action', () => {
  return expectSaga(onGetFruit, {
    payload: { name: 'apple' },
  })
    .provide([
      [select(selectFruit('apple')), {
        name: 'apple',
      }],
    ])
    .put({
      type: 'EAT_THE_FRUIT', payload: { 
        edible: true, 
        color: 'red',
      },
    })
    .run()
})

So, with this scenario I'm always getting an error like that:

  TypeError: Cannot read property 'fruits' of undefined
  > 2 | const selectFruit = name => state => state.fruits[name]
      |                                            ^

I've noticed that this doesn't happen for some selector similar to that, where I use some kind of memoize, like:

const memoizedSelectFruit = memoize(name => state => state.fruits[name])

I hope someone could help me with that! And thanks @jfairbank for this amazing package :)

movestill commented 4 years ago

We're seeing similar behavior with a selector with no arguments. We're using 4.0.0-beta.3.

diegofelipece commented 4 years ago

I've "worked around" this question by changing those selectors to pass the argument as a second parameter of the function, like it's suggested on redux-saga docs.

// So instead of:
const selectFruit = name => state => state.fruits[name]

// I have now:
const selectFruit = (state, name)  => state.fruits[name]

This way I can pass a second parameter to select, specifying the id, name or whatever. To clarify, I udated the same first example:

// Selector
const selectFruit = (state, name) => state.fruits[name]

// Saga
function* onGetFruit({ payload: { name } }) {
  const myFruit = yield select(selectFruit, name)

  if (myFruit === 'apple') {
    const apple = {
      edible: true, 
      color: 'red',
    }
    yield put(eatTheFruit(apple))
  }
}

// Test
it('should find an apple and dispatch an eat action', () => {
  return expectSaga(onGetFruit, {
    payload: { name: 'apple' },
  })
    .provide([
      [select(selectFruit, 'apple'), {
        name: 'apple',
      }],
    ])
    .put({
      type: 'EAT_THE_FRUIT', payload: { 
        edible: true, 
        color: 'red',
      },
    })
    .run()
})
movestill commented 4 years ago

For us, we're using a selector function that takes state as its only argument. provide() fails for us for this use case and I'd expect this would be the most common use case.

cpdwyer commented 4 years ago

I think you need to look at your code here. You are accessing an 'apple' key from the fruits object not a string. Your test does not reflect this.

Unless your state looks like ' state: { fruits: { apple: 'apple' } } '

you aren't going anywhere.

did you mean ? // Selector const selectFruit = state => state.fruits.name

no need for the second argument

then your provider would look like

[select(selectFruit), 'apple'],

mwarrier commented 2 years ago

Same issue here, selectors with parameters don't seem to be mocked

AaronV commented 2 years ago

I was able to test a curried selector-function by using matchers instead of select directly. I guess in your example it would be like this..

import * as matchers from 'redux-saga-test-plan/matchers';

...

.provide([
  [matchers.select.like(selectFruit('apple')), { name: 'apple' }],
])