С чего началось

А началось всё с того, что я полез в эти ваши чёртовы фронтенды. Как-то давно надоело мне ковырять рельсы, и теперь я ещё и на жабаскрипте пописываю иногда.

И приспичило мне в саге сделать изменение в состоянии redux-form. Соединим всё это с моим стремлением к написанию всего через тесты, и приехали. Всё сломалось.

Для особо нетерпеливых: можно сразу листать до раздела “решение”.

Что, собственно, случилось

Постарался привести максимально полный и приближённый к реальному код.

Код, который тестирует

import SagaTester from 'redux-saga-tester';
import sinon from 'sinon';
import { defer } from 'lodash';
import { change } from 'redux-form';
import getValueSaga from '../getValueSaga';
import SomeTypes from './SomeTypes';
import * as SomeActions from './SomeActions';

describe('getValueSaga test', () => {
  let sagaTester = null;
  let server = null;

  const path = '/get-value';
  const response = { value: true };

  beforeEach(() => {
    server = sinon.fakeServer.create();
    sagaTester = new SagaTester({ initialState });
    sagaTester.start(getValueSaga);
  });

  afterEach(() => {
    server = server.restore();
  });

  it('should get value and add it to form state', async () => {
    server.respondWith([200, {}, JSON.stringify(response)]);

    sagaTester.dispatch(SomeActions.getValue());

    defer(() => { server.respond(); });

    await sagaTester.waitFor(SomeTypes.GET_VALUE_SUCCESS);

    const request = server.requests[0];
    expect(request.url).toEqual(path);
    expect(request.method).toEqual('GET');

    expect(sagaTester.getCalledActions()).toContainEqual(change('some-form', 'value', response.value));
    expect(sagaTester.getLatestCalledAction()).toEqual(SomeActions.getValueSuccess());
  });
});

Код, который тестируется

import { call, put, takeLatest } from 'redux-saga/effects';
import { change } from 'redux-form';
import SomeTypes from './SomeTypes';
import * as SomeActions from './SomeActions';

function sendRequest(params) {
  const url = '/get-value';
  return axios.get(url).then(response => response.data);
}

function* getValueSaga(action) {
  try {
    const response = yield call(sendRequest);
    yield put(change('form-name', 'attribute-name', response.value));
    yield put(SomeActions.getValueSuccess());
  } catch (error) {
    // пропустим обработку ошибок
  }
}

export default function* watchGetValueSaga() {
  yield takeLatest(SomeTypes.GET_VALUE, getValueSaga);
}

Проблема

Вот на этой строке тест ломается

expect(sagaTester.getCalledActions()).toContainEqual(change('some-form', 'value', response.value));

С фразой “такой action не был вызван”. Wut?

Ладно, пробуем проверять так:

yield put({ type: "@@redux-form/change" });

Иии… Снова всё тоже, снова “такой action не был вызван”.

Хмм…

В результате долгих экспериментов я выяснил, что все action’ы, которые начинаются с @@redux куда-то исчезают. Совпадение? Не думаю.

Решение

Несколько часов (потому что я — идиот) поиска ключевой фразы сначала в redux, потом в redux-saga, потом в redux-form не дали ничего значимого. А вот поиск в redux-saga-tester дал интересный результат:

if (ignoreReduxActions && action.type.startsWith('@@redux')) {
  // Don't monitor redux actions

И далее поиск приводит сюда:

const sagaTester = new SagaTester({ignoreReduxActions: false});

И ни единой строчки про это в документации в redux-saga-tester, как и про то, что он вообще отбрасывает все action’ы, которые начинаются с @@redux.

В результате, изменение соответствующей строки на такую:

sagaTester = new SagaTester({ ignoreReduxActions: false, initialState });

решает проблему полностью.

А при чём тут Github, собственно?

А при том, что искать по строке @@redux он не даёт, как и не даёт искать вообще что-либо с символом @.

В результате, протратив время на поиск в github по всем библиотекам, я был вынужден потом клонировать репозиторий каждой и грепать локально. Позор, Github!