I'm learning in cyclejs and rxjs and I'm struggling to write a unit test for one of the components I'm working on. I'm sorry for the long question. I have tried to simplify the question and this is the best I can do.
Component does the following things:
1) When it's loaded, it sends a request to an end point to retrieve user's tasks. From these tasks, it finds the task user currently working on and creates an url stream which contains the endpoint which will be called when user clicks complete task button.
2) When a new value come to test stream, it creates a new stream from tests defined so far.
3) When user clicks complete task, component first sends a request to an endpoint to add tests user defined to the task. Then, if the call is successful, component sends another request to the endpoint which is retrieved at step 1 to complete task.
4) After complete task request is completed, component shows a notification to the user.
Here is the component:
import Rx from 'rx';
import {Observable} from 'rx';
const Model = ({test$, completeTask$, sources}) => {
const processId = Number(sources.routeParams.processId);
const SET_TEST_RESULTS = `http://dummyhost/tosettests/${processId}`;
const GET_USER_TASKS = 'http://dummyhost/togetusertasks';
const taskRequest$ = Observable.just({
url: GET_USER_TASKS,
method: 'GET'
});
const url$ = sources.HTTP
.filter(response => response.request.url === GET_USER_TASKS)
.flatMap(response => response.body)
.filter(task => task.processId === processId)
.take(1)
.map(task=> {
const activitiId = task.processInstanceId;
return {
completeTask: `http://dummyhost/tocompletetask/${activitiId}`
};
});
const definedTest$ = test$
.scan((acc, curr) => {
acc.push(curr);
return acc;
}, [])
.startWith([])
.share();
const setTestResultsRequest$ = completeTask$
.withLatestFrom(definedTest$, (event, tests) => {
return {
url: SET_TEST_RESULTS,
method: 'POST',
send: {
tests
}
};
});
const setTestResultsResponse$ = sources.HTTP
.filter(response => response.request.url === SET_TEST_RESULTS);
const completeTaskRequest$ = setTestResultsResponse$
.withLatestFrom(url$, (response, url) => {
return {
url: url.completeTask,
method: 'POST'
};
});
const completeTaskResponse$ = sources.HTTP
.withLatestFrom(url$, (response, url) => {
return {
response,
url
};
})
.filter(data => data.response.request.url === data.url.completeTask)
.map((data)=> data.response);
const successNotification$ = completeTaskResponse$
.map(() => {
return {
action: 'success',
message: 'Tests have been defined!'
};
});
const request$ = Observable.merge(taskRequest$, setTestResultsRequest$, completeTaskRequest$);
return {
request$: request$,
notification$: successNotification$,
definedTest$: definedTest$
};
};
What I'm trying to unit test are the followings:
1) Component must send a request to define tests to the task
2) Component must send a request to complete task
3) Component must show a notification
Here's the unit test I wrote so far with Mocha:
describe('When complete task is clicked', ()=> {
const tests = [
{
name: 'test 1 name'
},
{
name: 'test 2 name'
},
{
name: 'test 3 name'
}
];
const test$ = Observable.fromArray(tests);
const completeTask$ = Observable.just({}).delay(300);
const processId = 3;
const activitiId = 3;
var actions;
before(done => {
const HTTPSource = new Rx.ReplaySubject();
const sources = {
DOM: mockDOMSource(),
routeParams: {processId},
HTTP: HTTPSource
};
actions = Model({test$, completeTask$, sources});
const request$ = actions.request$.share();
request$
.filter(request=> request.url === 'http://dummyhost/togetusertasks')
.subscribe((request) => {
const response = {
request,
body: [{
processId,
processInstanceId: activitiId
}]
};
HTTPSource.onNext(response);
});
request$
.filter(request=> request.url === `http://dummyhost/tosettests/${processId}`)
.subscribe((request) => {
const response = {
request
};
HTTPSource.onNext(response);
});
request$
.filter(request=> request.url === `http://dummyhost/tocompletetask/${activitiId}`)
.subscribe((request) => {
const response = {
request
};
HTTPSource.onNext(response);
done();
});
});
it('should send a request to add tests', (done)=> {
actions.request$
.filter(request=> request.url === `http://dummyhost/tosettests/${processId}`)
.subscribe(() => {
done();
});
});
it('should send a request to complete task', (done)=> {
actions.request$
.filter(request=> request.url === `http://dummyhost/tocompletetask/${activitiId}`)
.subscribe(() => {
done();
});
});
it('should show a notification to inform user that tests have been defined', (done) => {
actions.notification$.take(1)
.subscribe(notification => {
expect(notification.action).to.equal('success');
done();
});
});
});
In the test, I have tried to mock HTTP source. By subscribing request stream sink returning from component, I'm able to create mock responses for each request. But I have to use share operator to run mocha's before function successfully. Otherwise, I'm only able to mock first request which is retrieving user's tasks. So, my first question is:
Why do I have to use share operator to run mocha's before function successfully?
Currently, tests that verify complete task request is sent and notification is shown passes. But, test that verifies define test to the task request is failing. This is strange because to verify complete task request is sent, component must send define tests to the task request first. It seems define tests to the task request somehow lost in the request stream returned from component's sink. So my second question is:
Why second and third tests are passing but first test is failing?
Here's a gist that contains all code in one file: gist link
As I said earlier, I'm new to cyclejs and rxjs. I think I'm not fully understanding what I'm testing. So any comments or answers about cyclejs and rxjs that directs me the right path are welcomed.
If you think question is not clear enough, please let me know. I'll do my best improve the question.