simple tape js test for redux saga failing with undefined - javascript

I've written a few tape tests for my sagas but the most simple examples are consistently failing for the same reason. I have a one line saga:
export function* clearUser(){
yield* put({type: 'CLEAR_USER'});
}
my tape test is equally simple:
test('clear user saga', (assert)=> {
const gen = clearUser();
assert.deepEqual(
gen.next().value,
put({type: 'CLEAR_USER'}),
'clear user should pass to reducer to remove user from state'
)
assert.deepEqual(
gen.next(),
{ done: true, value: undefined },
'clear user saga should complete'
)
assert.end()
});
However, the first assertion fails and says the value is undefined:
operator: deepEqual
expected: |-
{ '##redux-saga/IO': true, PUT: { action: { type: 'CLEAR_USER' }, channel: null } }
actual: |-
undefined
I've confirmed I'm importing the saga, and other tests are working, why does this simple test fail?

You need to yield your put effect, not delegate to another generator.
compare yield* vs yield

Related

Mocked function get triggered but toHaveBeenCalledWith do not recognize it

I mocked the useNavigation() hook from react-naitive/navigation but jest do not recognize it.
Jest gives me an error that the function did not get called, but when I log the function it got to be triggered.
I already tried it with wrapping they expect with waitFor but then I just get a typescript catch error
Error: Uncaught [TypeError: Cannot read properties of undefined (reading 'catch')]
My jest test:
jest.mock('#react-navigation/native', () => {
const actualNav = jest.requireActual('#react-navigation/native');
return {
...actualNav,
useNavigation: () => ({
// when I do () => { console.log('trigger') } it will be called
navigate: jest.fn(),
}),
useRoute: () => ({
params: { email: '' },
}),
};
});
....
it.only('should navigate to resend screen when button is pressed', async () => {
const { getByTestId } = render(
withStoreProvider(<EmailVerificationPending />),
);
await waitFor(() => {
expect(
getByTestId('emailVerificationPendingScreen_moreInfoCta'),
).toBeTruthy();
});
fireEvent.press(getByTestId('emailVerificationPendingScreen_moreInfoCta'));
expect(navigation.navigate).toHaveBeenCalledWith(
RootScreens.SignUpNavigator,
{
screen: SignUpScreens.EmailVerificationResend,
params: {
email: '',
},
},
);
});
Error message:
● features/signup/presentation/email-verification/pending-screen › should navigate to resend screen when button is pressed
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: "RootScreens_SignUpNavigator", {"params": {"email": ""}, "screen": "SignUpScreens_EmailVerificationResend"}
Number of calls: 0
64 | fireEvent.press(getByTestId('emailVerificationPendingScreen_moreInfoCta'));
65 |
> 66 | expect(navigation.navigate).toHaveBeenCalledWith(
This error message is likely occurring when you are trying to test a mocked function using the Jest testing framework, and the test is failing because the mocked function is being called but the assertion toHaveBeenCalledWith is not recognizing it.
Here are some possible solutions:
Make sure that you are importing the correct mock function and that
it is being called in the correct place in your code.
Check that the arguments passed to the mocked function match the
arguments passed to toHaveBeenCalledWith
check if the mock implementation is correct, you should use
jest.fn() or jest.spyOn() to create a mock function
Make sure that you are using the correct syntax for the
toHaveBeenCalledWith assert.
Try to debug the test to check if the mocked function is getting
called or not.
Make sure that you are correctly importing and calling the
navigation function, and that it is being called with the correct
arguments and parameters.
Make sure that the navigation function is being called after the
button press event and not before.
Make sure that the navigation function is being called in the
correct screen and the test is not looking for it in wrong screen.
Try using .toHaveBeenCalled() instead of .toHaveBeenCalledWith()
Make sure that the test case is not getting skipped or ignored.

Mocha finds global variable is undefined when extended by class

I have the following ES6 module from a Chromecast receiver that I would like to test using Mocha...
// SUT - app.js
import { Queue } from 'queue.js'
export const app = async () => {
const context = cast.framework.CastReceiverContext.getInstance();
const options = {};
options.queue = new Queue();
context.start(options);
}
This code runs inside a Chromecast user agent which provides access to the global cast object. This cast object exposes an api which in turn enables the JS thread to interact directly with the Chromecast CAF SDK. This cast variable is always available at run time.
The Queue class is slightly unusual because in order for it to work according to the CAF framework documentation, is must extend an abstract class from the framework cast.framework.QueueBase...
// queue.js
class Queue extends cast.framework.QueueBase {
initialize(){
// build queue here
}
}
Now I would like to write some unit tests to check my app function is correct. For example:
// app.test.js
import { app } from 'app.js';
it('should do some stuff', async function () {
// inject a mock cast object
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};
await app();
// Make some assertions
});
However, even though I am injecting a mock using global.cast, which is sufficient for all regular references to the cast object, in the case where a class is extending the injected cast object, apparently it is not yet available and I receive the following error:
ReferenceError: cast is not defined
I found an ugly hack to make this error disappear. If I place the following snippet above the class declaration then I can inject the mock at runtime and it not only works for Mocha but also for execution on the Chromecast device....
try {
// The following line throws in Mocha's node environment
// but executes fine on the Chromecast device
if (cast) {
}
} catch {
global.cast = {
framework: {
QueueBase: class {},
},
};
}
export class Queue extends cast.framework.QueueBase {
...
However, I would like to find a better solution so that I don't have to pollute my production code with this hack which is only there to allow me to run tests.
My .mocharc.yml file looks like this:
require:
- '#babel/register'
- 'ignore-styles'
- 'jsdom-global/register'
- 'babel-polyfill'
... and my command to run tests is:
mocha --recursive --use_strict
finally, my .babelrc file looks like this:
{
"presets": [
[
"#babel/preset-env"
]
],
"plugins": [
"inline-svg",
"import-graphql"
]
}
Static imports are always evaluated first, so the order of operations is roughly:
import { Queue } from 'queue.js'
class Queue extends cast.framework.QueueBase { // ReferenceError outside Chromecast!
initialize(){
// build queue here
}
}
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};
You can see that the mock is created after the reference to cast in app.js.
The only reliable way to run the mock creation before importing the app module is using a dynamic import:
// app.test.js
it('should do some stuff', async function () {
// inject a mock cast object
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};
const { app } = await import('app.js');
await app();
// Make some assertions
delete global.cast;
});
If you prefer not to repeat the mock creation and the import in every test, you can move both out of the test definition:
// app.test.js
// inject a mock cast object
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};
const { app } = await import('app.js');
it('should do some stuff', async function () {
await app();
// Make some assertions
});
// Optionally clean up the mock after all tests
after(() => delete global.cast);

TypeScript compiles successfully but output JS shows error

I'm new to TypeScript, and trying out custom type declaration through this simple snippet.
The script
I have this script:
// app.ts
type Customer = {
name: String,
isFrequentVisitor: Boolean,
}
type Order = {
buyer: Customer,
itemName: String,
itemPrice: number,
isConfirm: Boolean
}
function placeOrder (user: Customer): Order {
let order: Order
order.buyer = user
order.itemName = 'Raspberry Pi'
order.itemPrice = 1000
order.isConfirm = true
return order
}
let person: Customer = {
name: 'John',
isFrequentVisitor: false
}
let newOrder: Order = placeOrder(person)
if (newOrder.isConfirm) {
console.log('The order has been confirmed. Check details below:')
console.log(newOrder)
} else {
console.log('The order has not been confirmed yet.')
}
The problem
I'm able to run $ tsc app.ts successfully (without any errors on the console), and see app.js next to the file.
However, running $ node app.js, I get the following error -
/tmp/app.js:3
order.buyer = user;
^
TypeError: Cannot set property 'buyer' of undefined
Also, if it may be useful, I have linked the compiled app.js here.
I have worked with custom types in Golang, and they work just fine in a similar context.
I'm not sure what am I missing here.
Also, isn't it the reason behind using TypeScript, so that we can catch errors before they occur at runtime.
I'd appreciate a beginner-friendly answer. Thank you.
Specifications
using TypeScript Version 3.8.3
using Node version 14.4.0
To get more information from the compiler you have to use compiler options for tsc or a tsconfig.json file.
tsc --strictNullChecks index.ts
The command above with --strictNullChecks option should display information like this:
index.ts:15:3 - error TS2454: Variable 'order' is used before being assigned.
15 order.buyer = user
~~~~~
index.ts:16:3 - error TS2454: Variable 'order' is used before being assigned.
16 order.itemName = 'Raspberry Pi'
~~~~~
index.ts:17:3 - error TS2454: Variable 'order' is used before being assigned.
17 order.itemPrice = 1000
~~~~~
index.ts:18:3 - error TS2454: Variable 'order' is used before being assigned.
18 order.isConfirm = true
~~~~~
index.ts:19:10 - error TS2454: Variable 'order' is used before being assigned.
19 return order
~~~~~
Found 5 errors.
You need to initialize the variable with some default value, or you can just fill the value like the code below.
function placeOrder (user: Customer): Order {
let order: Order = {
buyer: user,
itemName: 'Raspberry Pi',
itemPrice: 1000,
isConfirm: true
};
return order
}
Or you can also do type assertion
function placeOrder (user: Customer): Order {
let order = {} as Order;
order.buyer = user
order.itemName = 'Raspberry Pi'
order.itemPrice = 1000
order.isConfirm = true
return order
}
Providing default value is actually no different than my first snippet. Just put it the value. For 'string' we put empty string '', boolean we put false and so on. After that, you can do what you want with the variable.
Snippets for providing default value:
type exampleType = {
exampleBoolean: Boolean,
}
let exampleInstance : exampleType = {
exampleBoolean: false
}
exampleInstance.exampleBoolean = true //change to some real value

Can't unit test redux-saga with selector function using Jest

Problem Explanation:
I want to unit test a redux-saga using Jest. I'm doing this the way it is described in the example provided within the redux-saga docs: https://redux-saga.js.org/docs/advanced/Testing.html
Within my Saga I'm calling a selector function selectSet that returns a specific object from the application store:
export const selectSet = state => state.setStore.set
In my saga I'm trying to yield this selector function:
import { put, select } from 'redux-saga/effects'
import { selectSet } from '../selectors'
export function* getSet() {
try {
const set = yield select(selectSet)
yield put({ type: 'SET_SUCCESS', payload: { set } })
} catch (error) {
yield put({ type: 'SET_ERROR', payload: { error } })
}
}
Within my test there is no valid application store so I'd have to mock the function to return the expected object:
import assert from 'assert'
import * as AppRoutines from './AppRoutines'
import { put, select } from 'redux-saga/effects'
describe('getSet()', () => {
it('should trigger an action type "SET_SUCCESS" with a payload containing a valid set', () => {
const generator = AppRoutines.getSet()
const set = {
id: 1,
slots: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }],
}
const selectSet = jest.fn()
selectSet.mockReturnValue(set)
// Saga step 1
const actualFirst = generator.next().value
const expectedFirst = select(selectSet)
assert.deepEqual(
actualFirst,
expectedFirst,
'it should retreive a valid set from the store using the selectSet selector'
)
})
})
However - if I assert the saga to return a specific generator value using deepEqual and my mocked function, it expects my selector function to have the original selectSet constructor. But since I'm mocking the function with jest.fn() the constructor is actually equal to mockConstructor - which makes my test fail:
Expected value to deeply equal to:
{"##redux-saga/IO": true, "SELECT": {"args": Array [], "selector": [Function mockConstructor]}}
Received:
{"##redux-saga/IO": true, "SELECT": {"args": Array [], "selector": [Function selectSet]}}
Question: How can I make an assert.deepEqual containing a mock function without conflicting constructor types?
Alternative Question: Is there a way to make my assertion expect a mockConstructor instead of the actual selectSet constructor?
You should not need to mock the selector at all, as in a saga test of this nature, the selector is never actually called, instead you are testing the declarative instructions that are created for the redux saga middleware to act upon are as you expect
This is the instruction that the saga will create {"##redux-saga/IO": true, "SELECT": {"args": Array [], "selector": [Function selectSet]}}, but as the middleware is not running during this test scenario selectSelect will never actually get called
If you need to mock results that your selector returns for your action, then you do so by passing the mock data into the next step...
const set = {
id: 1,
slots: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }],
}
// Saga step 1
const firstYield = generator.next().value
assertDeepEqual(firstYield, select(selectSet))
// Step 2 - successful so dispatch action
// mock data from the previous yield by passing into this step via `next(stubbedYieldedData)`
const secondYield = generator.next(set).value
assertDeepEqual(secondYield, put({type: 'SET_SUCCESS', payload: {set} }))
We can pass the mock store in a fake store as below. Below are the sample selector and generator function along with its test.
Selector
const authSelector = (state) => state.authReducer || initialState;
Saga Generator function
export function* getAuthToken(action) {
try {
const authToken = yield select(makeSelectAuthToken());
} catch (errObj) {}
}
Test Case
import { runSaga } from 'redux-saga'
const dispatchedActions = [];
const fakeStore = {
getState: () => ({ authReducer: { auth: 'test' } }),
dispatch: (action) => dispatchedActions.push(action)
}
await runSaga(fakeStore, getAuthToken, {
payload: {}
}).done;
expected case you can write here below this

Test Redux-Saga 'yield call'

I'm trying to write a unit test to the following saga :
function * verifyCode(api){
let action = yield take(LoginTypes.VERIFY_CODE)
const phoneNumber = yield select(phoneNumberSelector)
try{
const response = yield call(api.verifyCode, phoneNumber, action.code)
if (response.ok){
let token = response.data.token
yield put(LoginActions.verifyCodeSuccess(response.data.token))
}
else {
yield put(LoginActions.verifyCodeFailure(response.error))
}
}
catch(error){
yield put(LoginActions.verifyCodeFailure(error))
}
}
All the tests pass up until the 'yield call' part, using the following (using test from 'tape'):
test('verify code flow', (assert) => {
let number = '0000000'
let code = '00000'
const gen = verifyCode(api)
assert.deepEqual(
gen.next({code: code}).value,
take(LoginTypes.VERIFY_CODE),
'wait for verify code action from user',
)
assert.deepEqual(
gen.next().value,
select(phoneNumberSelector),
'get phoneNumber from login state'
)
assert.deepEqual(
gen.next().value,
call(api.verifyCode, number, code),
'call verify code'
)
assert.end()
})
The error I get for the failure of this test is
operator: deepEqual
expected: |-
{ '##redux-saga/IO': true, CALL: { context: null, fn: [Function: verifyCode], args: [ '0000000', '00000' ] } }
actual: |-
{ '##redux-saga/IO': true, PUT: { channel: null, action: { type: 'VERIFY_CODE_FAILURE', error: [TypeError: Cannot read property 'code' of undefined] } } }
What is the correct way to write to an api call using the 'call' effect?
How can I test the different possible flows depending on the ''response I get?
This is the correct way to use the call effect on an API
To test different flows (Success | Failure) you can use the following pattern (in Jest):
expect(generator.next().value.PUT.action).toMatchObject({type: types.VERIFY_CODE_SUCCESS, payload: {}, meta: { section }});
expect(generator.throw('error').value.PUT.action).toMatchObject({ type: VERIFY_CODE_FAILURE, payload: 'error'});
BTW: It seems that your test fails due to an error thrown when you called your API endpoint. Make sure you get the desired response from your endpoint.
Good Luck!

Categories

Resources