1

I have a createPostSaga which POSTs to my API and I am trying to test it. This is the saga:

export function* createPostSaga(action) {
  const token = yield select(selectToken);
  const headerParams = {
    Authorization: `JWT ${token}`
  };
  const apiCall = () => {
    let formData = new FormData();
    formData.append("title", action.payload.title);
    formData.append("content", action.payload.content);
    formData.append("thumbnail", action.payload.thumbnail);
    formData.append("tags", JSON.stringify(action.payload.tags));
    formData.append("slug", generateSlug(action.payload.title));
    return axios({
      method: "post",
      url: "/posts/",
      data: formData,
      headers: headerParams
    })
      .then(response => response.data)
      .catch(err => {
        throw err;
      });
  };

  try {
    const response = yield call(apiCall);
    yield put(push(`/posts/${response.id}/${response.slug}/`));
  } catch (error) {
    console.log(error);
  }
}

and this is the code I have so far that is failing:

describe("createPostSaga", () => {
    const action = {
      type: types.CREATE_POST,
      payload: {
        title: "test title",
        pub_date: "2018-11-12",
        content: "test content",
        tags: ["test1", "test2"],
        thumbnail: "http://fail",
        slug: "test-title"
      }
    }

    const apiCall = () => ({
      id: 1,
      ...action.payload  
    });

    it("calls the api and redirects", () => {
      testSaga(actions.createPostSaga, action)
        .next()
        .select(selectToken)
        .next()
        .call(apiCall)
        .next()
        .put(push("/posts/1/test-title/"))
        .finish()
        .isDone();
    })
  })

When I test this code, I receive the following error:

SagaTestError:
    Assertion 2 failed: call effects do not match

    Expected
    --------
    { context: null, fn: [Function: apiCall], args: [] }

    Actual
    ------
    { context: null, fn: [Function: apiCall], args: [] }

As you can see the expected and actual results are the same.

I have also tried to directly copy paste the apiCall function into my test but it fails with the same message and I am stumped.

Lin Du
  • 88,126
  • 95
  • 281
  • 483
CHBresser
  • 185
  • 2
  • 2
  • 15

1 Answers1

0

Before testing, you should refactor your code. Extract the apiCall function from the createPostSaga generator function, you should keep using the same apiCall function both in the createPostSaga and the test case.

Besides, you are using the testSaga function to do unit testing, but we still need to provide mock return value for yield select(selectToken) and yield call(apiCall, action, headerParams). We can do this by calling the .next(mockValue) function and passing a value to it.

The final working example:

saga.ts:

import axios from "axios";
import { call, put, select } from "redux-saga/effects";

export const selectToken = state => state.token;
export const push = (payload) => ({ type: 'PUSH', payload })

export const apiCall = (action, headerParams) => {
  let formData = new FormData();
  formData.append("title", action.payload.title);
  formData.append("content", action.payload.content);
  formData.append("thumbnail", action.payload.thumbnail);
  formData.append("tags", JSON.stringify(action.payload.tags));
  formData.append("slug", action.payload.title);
  return axios({
    method: "post",
    url: "/posts/",
    data: formData,
    headers: headerParams
  })
    .then(response => response.data)
    .catch(err => {
      throw err;
    });
};

export function* createPostSaga(action) {
  const token = yield select(selectToken);
  const headerParams = {
    Authorization: `JWT ${token}`
  };
  try {
    const response = yield call(apiCall, action, headerParams);
    yield put(push(`/posts/${response.id}/${response.slug}/`));
  } catch (error) {
    console.log(error);
  }
}

saga.test.ts:

import { testSaga } from "redux-saga-test-plan";
import * as actions from './saga'
import { apiCall, push, selectToken } from "./saga";

const types = {
  CREATE_POST: 'CREATE_POST'
}

describe('53423270', () => {
  const action = {
    type: types.CREATE_POST,
    payload: {
      title: "test title",
      pub_date: "2018-11-12",
      content: "test content",
      tags: ["test1", "test2"],
      thumbnail: "http://fail",
      slug: "test-title"
    }
  }

  it("calls the api and redirects", () => {
    testSaga(actions.createPostSaga, action)
      .next()
      .select(selectToken)
      .next('fakeTokenAbcd123')
      .call(apiCall, action, { Authorization: 'JWT fakeTokenAbcd123' })
      .next({ id: '1', slug: 'test-title' })
      .put(push("/posts/1/test-title/"))
      .finish()
      .isDone();
  })
})

Test result:

 PASS   redux-saga-examples  packages/redux-saga-examples/src/stackoverflow/53423270/saga.test.ts
  53423270
    ✓ calls the api and redirects (3 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |   59.26 |      100 |   33.33 |      50 |                   
 saga.ts  |   59.26 |      100 |   33.33 |      50 | 8-22,35           
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.535 s

package versions:

"redux-saga": "^1.1.3",
"redux-saga-test-plan": "^4.0.1",
"jest": "^27.0.6",
"axios": "^1.1.3"
Lin Du
  • 88,126
  • 95
  • 281
  • 483