What is the point of unit testing redux-saga watchers? - unit-testing

In order to get 100% coverage of my Saga files I'm looking into how to test watchers.
I've been googling around, there are several answers as to HOW to test watchers. That is, saga's that do a takeEvery or takeLatest.
However, all methods of testing seem to basically copy the implementation. So what's the point of writing a test if it's the same?
Example:
// saga.js
import { delay } from 'redux-saga'
import { takeEvery, call, put } from 'redux-saga/effects'
import { FETCH_RESULTS, FETCH_COMPLETE } from './actions'
import mockResults from './tests/results.mock'
export function* fetchResults () {
yield call(delay, 1000)
yield put({ type: FETCH_COMPLETE, mockResults })
}
export function* watchFetchResults () {
yield takeEvery(FETCH_RESULTS, fetchResults)
}
Test method 1:
import { takeEvery } from 'redux-saga/effects'
import { watchFetchResults, fetchResults } from '../sagas'
import { FETCH_RESULTS } from '../actions'
describe('watchFetchResults()', () => {
const gen = watchFetchResults()
// exactly the same as implementation
const expected = takeEvery(FETCH_RESULTS, fetchResults)
const actual = gen.next().value
it('Should fire on FETCH_RESULTS', () => {
expect(actual).toEqual(expected)
})
})
Test method 2: with a helper, like Redux Saga Test Plan
It's a different way of writing, but again we do basically the same as the implementation.
import testSaga from 'redux-saga-test-plan'
import { watchFetchResults, fetchResults } from '../sagas'
import { FETCH_RESULTS } from '../actions'
it('fire on FETCH_RESULTS', () => {
testSaga(watchFetchResults)
.next()
.takeEvery(FETCH_RESULTS, fetchResults)
.finish()
.isDone()
})
Instead I'd like to simply know if watchFestchResults takes every FETCH_RESULTS. Or even only if it fires takeEvery(). No matter how it follows up.
Or is this really the way to do it?

It sounds like the point of testing them is to achieve 100% test coverage.
There are some things that you can unit test, but it is questionable if you should.
It seems to me that this situation might be a better candidate for an 'integration' test. Something that does not test simply a single method, but how several methods work together as a whole. Perhaps you could call an action that fires a reducer that uses your saga, then check the store for the resulting change? This would be far more meaningful than testing the saga alone.

I agree with John Meyer's answer that this is better suited for the integration test than for the unit test. This issue is the most popular in GitHub based on up votes. I would recommend reading it.
One of the suggestions is to use redux-saga-tester package created by opener of the issue. It helps to create initial state, start saga helpers (takeEvery, takeLatest), dispatch actions that saga is listening on, observe the state, retrieve a history of actions and listen for specific actions to occur.
I am using it with axios-mock-adapter, but there are several examples in the codebase using nock.
Saga
import { takeLatest, call, put } from 'redux-saga/effects';
import { actions, types } from 'modules/review/reducer';
import * as api from 'api';
export function* requestReviews({ locale }) {
const uri = `/reviews?filter[where][locale]=${locale}`;
const response = yield call(api.get, uri);
yield put(actions.receiveReviews(locale, response.data[0].services));
}
// Saga Helper
export default function* watchRequestReviews() {
yield takeLatest(types.REVIEWS_REQUEST, requestReviews);
}
Test example using Jest
import { takeLatest } from 'redux-saga/effects';
import { types } from 'modules/review/reducer';
import SagaTester from 'redux-saga-tester';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import watchRequestReviews, { requestReviews } from '../reviews';
const mockAxios = new MockAdapter(axios);
describe('(Saga) Reviews', () => {
afterEach(() => {
mockAxios.reset();
});
it('should received reviews', async () => {
const services = [
{
title: 'Title',
description: 'Description',
},
];
const responseData = [{
id: '595bdb2204b1aa3a7b737165',
services,
}];
mockAxios.onGet('/api/reviews?filter[where][locale]=en').reply(200, responseData);
// Start up the saga tester
const sagaTester = new SagaTester({ initialState: { reviews: [] } });
sagaTester.start(watchRequestReviews);
// Dispatch the event to start the saga
sagaTester.dispatch({ type: types.REVIEWS_REQUEST, locale: 'en' });
// Hook into the success action
await sagaTester.waitFor(types.REVIEWS_RECEIVE);
expect(sagaTester.getLatestCalledAction()).toEqual({
type: types.REVIEWS_RECEIVE,
payload: { en: services },
});
});
});

Related

Unit testing nestjs guards Unknown authentication strategy

Trying to write unit tests as described here but no idea how to get around this error
Exception has occurred: Error: Unknown authentication strategy "test-jwt"
at attempt (/home/user/Workspace/project/node_modules/passport/lib/middleware/authenticate.js:190:39)
at authenticate
auth file
import { Injectable } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
#Injectable()
export class MyGuard extends AuthGuard('test-jwt') { }
test
import { ExecutionContext } from "#nestjs/common";
import { MyGuard } from "./mygaurd";
it('test' () => {
const context: ExecutionContext = {
switchToHttp: () => context,
getRequest: () => {
return {
headers: {
authorization: `bearer ${jwt}`
}
}
},
getResponse: () => { }
} as unknown as ExecutionContext
const guard = new MyGuard()
expect(guard.canActivate(context)).toBeTrue();
})
The actual implementation works fine, I add it to a controller.
#UseGuards(MyGuard)
export class MyController {
I don't even need to add it as a provider or anything in my setup so not sure what other code to include.
I implemented a custom strategy which may be related
import { Strategy, ExtractJwt } from "passport-jwt";
import { Injectable } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
#Injectable()
export class MyStrategy extends PassportStrategy(Strategy, 'test-jwt') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'secret'
})
}
async validate(payload) {
...
}
}
And of course MyStrategy is added as a provider in my app.
I have unit tests covering my custom strategy already so it is really just the guard left
EDIT:
Trying Jay's suggestion below gets me a little closer but i'm still struggling.
It seems passport.use() expects a name and Strategy rather than a function (so TS compilation fails) so I tried
import passport, { Strategy } from "passport";
...
passport.use('test-jwt', {
authenticate: (payload) => true
} as Strategy);
and the error disappears, but the test now outputs
expect(received).toBeTrue()
Expected value to be true:
true
Received:
{}
Any further suggestions?
This is one of those quirky things with passport that I never thought I'd see. So, passport uses strategy names to determine what authentication method is actually being used, right? All of these strategies get registered to the passport context using passport.use(name, method) in the general scheme of things. In the context of Nest, this happens when you create a custom strategy, extend PassportStrategy and add the strategy as a provider as seen here. Later, the method passport.authenticate(strategy, (err, req, res, next) is called during the AuthGuard#canActivate method (the codes a bit complex, but this is where it happens). Because passport has never seen passport.use('test-jwt', authMethod) in the context of your test, it ends up not knowing what to do other than throwing the error about "Unknown authentication strategy".
Normally, the validate method is what becomes the authMethod, but if you're just needing this for the context of your test you can do something like
it('test' () => {
passport.use('test-jwt', (payload) => true);
const context: ExecutionContext = {
switchToHttp: () => context,
getRequest: () => {
return {
headers: {
authorization: `bearer ${jwt}`
}
}
},
getResponse: () => { }
} as unknown as ExecutionContext
const guard = new MyGuard()
expect(guard.canActivate(context)).toBeTrue();
})
and it should work out all right. You an then modify the value returned from that method, or make it a jest.fn() so that you can check what it was called with and modify what it returns if you need to do extra testing on the guard.

Vue with jest - Test with asynchronous call

How to make my test wait for the result of my api?
I'm using vue and jest to test my components.
I want to test the method that writes a client to my database. In my component I have the following method:
methods: {
onSubmitClient(){
axios.post(`urlApi`, this.dados).then(res => {
return res;
})
}
}
in my test
describe('login.vue', () => {
let wrapper
beforeAll(()=>{
wrapper = mount(client, {
stubs: ['router-link'],
store,
data() {
return {
dados: {
name: 'tes name',
city: 'test city'
},
};
}
})
})
it('store client', () => {
res = wrapper.vm.onSubmitLogin()
console.log(res);
})
})
My test does not wait for the API call to complete. I need to wait for the API call to know if the test worked. How can I make my test wait for API return?
There are several issues in your code.
First, you cannot return from an async call. Instead, you should be probably setting up some data in your onSubmitClient, and returning the whole axioscall, which is a Promise. for instance:
onSubmitClient(){
return axios.post(`urlApi`, this.dados).then(res => {
this.result = res;
return res;
})
}
I assume the method here is storing a result from the server. Maybe you don't want that; it is just an example. I'll come back to it later.
Ok, so now, you could call onSubmitClient in your wrapper and see if this.result is already set. As you already know, this does not work straightforward.
In order for a jest test to wait for asynchronous code, you need either to provide a done callback function or return a promise. I'll show an example with the former:
it('store client', (done) => {
wrapper.vm.onSubmitLogin().then((res) => {
expect(wrapper.vm.dados).toEqual(res);
done();
})
});
Now this code should just work, but still there is an issue with it, as #jonsharpe says in a comment.
You usually don't want to perform real network requests in unitary tests because they are slow and unrealiable. Also, unitary tests are meant to test components in isolation, and here we are testing not only that our component sets this.result properly when the request is made. We are also testing that there is a webserver up and running that is actually working.
So, what I would do in this scenario to test that single piece of functionality, is to extract the request to another method, mock it with vue-test-utils and jest.fn, and then assert that onSubmitClient does its work:
The component:
export default {
data() {
return {
http: axios,
...
},
methods: {
onSubmitClient(){
this.http.post(`urlApi`, this.dados).then(res => {
this.result = res;
})
}
}
}
}
The test:
it('store client', (done) => {
const fakeResponse = {foo: 'bar'};
var post = jest.fn();
var http : {
post,
};
var wrapper = mount(client, {
stubs: ['router-link'],
store,
data() {
return {
dados: {
name: 'tes name',
city: 'test city'
},
http, //now, the component under test will user a mock to perform the http post request.
}
}
});
wrapper.vm.onSubmitLogin().then( () => {
expect(post).toHaveBeenCalled();
expect(wrapper.vm.result).toEqual(fakeResponse);
done();
})
});
Now, your test asserts two things:
post gets called.
this.result is set as it should be.
If you don't want to store anything in your component from the server, just drop the second assertion and the this.result = res line in the method.
So basically this covers why your test is not waiting for the async request and some issues in your code. There are still some things to consider (f.i. I think a global wrapper is bad idea, and I would always prefer shallowMount over mount when testing components behavior), but this answer should help you a lot.
PS: didn't test the code, so maybe I messed up something. If the thing just doesn't work, look for syntax errors or similar issues.

Is it possible to use TypeScript with 'aws-sdk-mock'

I'm writing unit tests for a serverless application in TypeScript, and I'd like to mock the AWS SDK.
Unfortunately I have not found many existing type definitions for popular AWS mocking projects. In particular I'd like to use the aws-sdk-mock library, but without its type definitions I can't.
Theoretically I'd like to be able to do something like:
import 'jest';
import * as sinon from 'sinon';
import * as _ from 'lodash';
import { handler } from '../lib/lambda';
import AWSMock from 'aws-sdk-mock';
import { PutItemInput } from 'aws-sdk/clients/dynamodb';
const mockData: DataType = {
// ...some fields
};
describe('create data lambda tests', () => {
afterEach(() => {
sinon.restore();
AWSMock.restore();
});
it('returns a success response on creation', () => {
AWSMock.mock('DynamoDB.DocumentClient', 'put', (params: PutItemInput, callback: any) => {
return callback(null, 'Successful creation');
});
const mockGatewayEvent: any = {
headers: {
Authorization: // some JWT
},
body: _.clone(mockData)
};
handler(mockGatewayEvent).then((createdData: DataType) => {
expect(createdData.id).toBeDefined();
expect(createdData.id.length).toBeGreaterThan(0);
}, () => {
fail('The create request should not have failed');
});
});
});
Here's how we got it working with jest. This tests a lambda function that makes calls to Dynamo using the DynamoDB.DocumentClient.
The warnings about importing the aws-sdk-mock ts definitions go away for me if the file is called *.test.ts or *.spec.ts.
// stubbed.test.ts
// this line needs to come first due to my project's config
jest.mock("aws-sdk");
import * as AWS from "aws-sdk-mock";
import { handler } from "../index";
// these next two are just test data
import { mockDynamoData } from "../__data__/dynamo.data";
import { mockIndexData } from "../__data__/index.data";
describe("Stubbed tests", () => {
it("should return correct result when Dynamo returns one slice", async () => {
expect.assertions(2);
const mockQuery = jest.fn((params: any, cb: any) =>
cb(null, mockDynamoData.queryOneSlice)
);
AWS.mock("DynamoDB.DocumentClient", "query", mockQuery);
// now all calls to DynamoDB.DocumentClient.query() will return mockDynamoData.queryOneSlice
const response = await handler(mockIndexData.handlerEvent, null, null);
expect(mockQuery).toHaveBeenCalled();
expect(response).toEqual(mockIndexData.successResponseOneSlice);
AWS.restore("DynamoDB.DocumentClient");
});
});

How to test react component correctly?

Recently I am learning to test React with jest and enzyme, It seems hard to understand what a unit test is it, my code
import React from "react";
class App extends React.Component {
constructor() {
super();
this.state = {
value: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const value = e.target.value;
this.setState({
value
});
}
render() {
return <Nest value={this.state.value} handleChange={this.handleChange} />;
}
}
export const Nest = props => {
return <input value={props.value} onChange={props.handleChange} />;
};
export default App;
and my test
import React from "react";
import App, { Nest } from "./nest";
import { shallow, mount } from "enzyme";
it("should be goood", () => {
const handleChange = jest.fn();
const wrapper = mount(<App />);
wrapper.find("input").simulate("change", { target: { value: "test" } });
expect(handleChange).toHaveBeenCalledTimes(1);
});
IMO, the mocked handleClick will intercept the handleClick on App,
if this is totally wrong, what's the right way to use mock fn and test the handleClick be called.
Another: I search a lot, read the similar situations, seem like this iscontra-Unit Test,
Probably I should test the two component separately, I can test both components,
test the
<Nest value={value} handleChange={handleChange} />
by pass the props manually, and then handleChangeinvoked by simulate change
it passed test.
but how can I test the connection between the two?
I read
some work is React Team's Work
...
I don't know which parts I have to test in this case, and Which parts react already tested and don't need me to test. That's confusing.
You should take the path of testing the Nest component in isolation first, passing your mocked handleChange as a prop, to verify that input changes are being propagated.
If you want to test the state part, then you can get the instance of your App class from enzyme and call that method directly:
it("should update the Nest value prop when change is received", () => {
const wrapper = mount(<App />);
const instance = wrapper.instance()
instance.handleChange( { target: { value: "test" } })
const nestComponent = wrapper.find("Nest").first()
expect(nestComponent).prop('value').toEqual('test');
});
This a very very basic, almost not needed to test piece of code, but it will get your test coverage up if that's what you're after.
Doc for instance: http://airbnb.io/enzyme/docs/api/ReactWrapper/instance.html
If you want to test for the connection. From what I see, the nest component is a child component inside the App component. You could test that <App /> contains `.
describe('<App />', () => {
it('should contain a nest component', () => {
const wrapper = mount(<App />);
expect(wrapper.find(<Nest />)).toHaveLength(1);
});
});
Secondly, since the onChange event on the nest component updates the state in the App component, you can also test for state changes since its a behavior you expect.
it('should update state', () => {
//find input and simulate change with say {value: 'new value'} and then
expect(wrapper.state().value).toBe('newValue');
});
I hope this helps.

Jest -- Mock a function called inside a React Component

Jest provides a way to mock functions as described in their docs
apiGetMethod = jest.fn().mockImplementation(
new Promise((resolve, reject) => {
const userID = parseInt(url.substr('/users/'.length), 10);
process.nextTick(
() => users[userID] ? resolve(users[userID]) : reject({
error: 'User with ' + userID + ' not found.',
});
);
});
);
However these mocks only seem to work when the function is called directly in a test.
describe('example test', () => {
it('uses the mocked function', () => {
apiGetMethod().then(...);
});
});
If I have a React Component defined as such how can I mock it?
import { apiGetMethod } from './api';
class Foo extends React.Component {
state = {
data: []
}
makeRequest = () => {
apiGetMethod().then(result => {
this.setState({data: result});
});
};
componentDidMount() {
this.makeRequest();
}
render() {
return (
<ul>
{ this.state.data.map((data) => <li>{data}</li>) }
</ul>
)
}
}
I have no idea how to make it so Foo component calls my mocked apiGetMethod() implementation so that I can test that it renders properly with data.
(this is a simplified, contrived example for the sake of understanding how to mock functions called inside react components)
edit: api.js file for clarity
// api.js
import 'whatwg-fetch';
export function apiGetMethod() {
return fetch(url, {...});
}
You have to mock the ./api module like this and import it so you can set the implemenation of the mock
import { apiGetMethod } from './api'
jest.mock('./api', () => ({ apiGetMethod: jest.fn() }))
in your test can set how the mock should work using mockImplementation:
apiGetMethod.mockImplementation(() => Promise.resolve('test1234'))
In case the jest.mock method from #Andreas's answer did not work for you. you could try the following in your test file.
const api = require('./api');
api.apiGetMethod = jest.fn(/* Add custom implementation here.*/);
This should execute your mocked version of the apiGetMethod inside you Foo component.
Here is an updated solution for anyone struggling with this in '21. This solution uses Typescript, so be aware of that. For regular JS just take out the type calls wherever you see them.
You import the function inside your test at the top
import functionToMock from '../api'
Then you indeed mock the call to the folder outside of the tests, to indicate that anything being called from this folder should and will be mocked
[imports are up here]
jest.mock('../api');
[tests are down here]
Next we mock the actual function we're importing. Personally I did this inside the test, but I assume it works just as well outside the test or inside a beforeEach
(functionToMock as jest.Mock).mockResolvedValue(data_that_is_returned);
Now here's the kicker and where everyone seems to get stuck. So far this is correct, but we are missing one important bit when mocking functions inside a component: act. You can read more on it here but essentially we want to wrap our render inside this act. React testing library has it's own version of act. It is also asynchronous, so you have to make sure your test is async and also define the destructured variables from render outside of it.
In the end your test file should look something like this:
import { render, act } from '#testing-library/react';
import UserGrid from '../components/Users/UserGrid';
import { data2 } from '../__fixtures__/data';
import functionToMock from '../api';
jest.mock('../api');
describe("Test Suite", () => {
it('Renders', async () => {
(functionToMock as jest.Mock).mockResolvedValue(data2);
let getAllByTestId: any;
let getByTestId: any;
await act(async () => {
({ getByTestId, getAllByTestId } = render(<UserGrid />));
});
const container = getByTestId('grid-container');
const userBoxes = getAllByTestId('user-box');
});
});
Another solution to mock this would be:
window['getData'] = jest.fn();