The Get Speaker action

We now have a mock API to call that behaves the way we want it to. Next on our list is creating the actions that will handle the results from our mock API. For the process of getting a speaker, we will need two actions. One of the actions will notify the application about a successful find and provide the found speaker to the reducers. The other action will notify the application about the failure to find the requested speaker.

Let's write a test to confirm its existence. This test should be inside the synchronous tests section of the speaker actions tests. We will also want to create a new describe for the get speaker success action.

describe('Find Speaker Success', () => {
it('exists', () => {

     expect(speakerActions.getSpeakerSuccess).to.exist;
});
});

To make this test pass, we just create the action function.

export function getSpeakerSuccess() {
}

Now we need to verify the return value of the action. Just like our get all speakers success action, the get speaker success action will receive the found speaker and return an object containing a type and the speaker data. Let's write the test for that now.

it('is created with correct data', () => {
// arrange
const speaker = {
id: 'test-speaker',
firstName: 'Test',
lastName: 'Speaker'
};

// act
const result = speakerActions.getSpeakerSuccess(speaker);

// assert
expect(result.type).to.equal(types.GET_SPEAKER_SUCCESS);
expect(result.speaker).to.deep.equal(speaker);
});

This test is fairly straightforward so let's look at the production code to pass it.

export function getSpeakerSuccess(speaker) {
return { type: types.GET_SPEAKER_SUCCESS, speaker: speaker };
}

Again, this code is straightforward. Next, let's handle the failure action. We will need to create a new describe for this test as well.

describe('Get Speaker Failure', () => {
it('exists', () => {
expect(speakerActions.getSpeakerFailure).to.exist;
});
});

Nothing new here, you should be starting to get a feel for the flow by now. Let's keep going and make this test pass.

export function getSpeakerFailure() {
}

The data we should be getting back for a failure to retrieve a speaker should be the SPEAKER_NOT_FOUND error type. In our next test, we will receive that error and create the action type from it.

it('is created with correct data', () => {
// arrange
const error = {
type: errorTypes.SPEAKER_NOT_FOUND
};

// act
const result = speakerActions.getSpeakerFailure(error);

// assert
expect(result.type).to.equal(types.GET_SPEAKER_FAILURE);
expect(result.error).to.deep.equal(error);
});

Making this test pass is very similar to the implementation for the other synchronous actions.

export function getSpeakerFailure(error) {
return { type: types.GET_SPEAKER_FAILURE, error: error }
}

Looking at the code, there is one important difference. This code doesn't have speaker data. The reason is because this action will need to be handled by a different reducer, an error reducer. We will create the error reducer and error component shortly. But first, we need to create the asynchronous action that will make the call to the mock API.

In testing the asynchronous action to get speakers, we should start with the failure case. In this case, the failure case is GET_SPEAKER_FAILURE. Here is a test to ensure the correct secondary action is triggered.

it('creates GET_SPEAKER_FAILURE when speaker is not found', () => {
// arrange
const speakerId= 'not-found-speaker';
const store = mockStore({
speaker: {}
});

// act
return (
store.dispatch(speakerActions.getSpeaker(speakerId)).then(() => {
const actions = store.getActions();

// assert
console.log(actions);
expect(actions[0].type).to.equal(types.GET_SPEAKER_FAILURE);
})
);
});

The code to make this test pass is similar to the code we have for getting all the speakers.

export function getSpeaker(speakerId) {
return function(dispatch) {
return new MockSpeakerService().getById(speakerId).catch(err => {
dispatch(getSpeakerFailure(err));
});
};
}

Here, we have called the mock API and we expect it to reject the promise, resulting in the dispatching of the getSpeakerFailure action.

Our next test is the successful retrieval of a specific speaker. We do have a problem though. You may have noticed that we are creating a new MockSpeakerService for each asynchronous action. This is problematic because it prevents us from pre-populating our mock API with values for the test. Later in the development of this application, the back-end will be ready and we will want to point our front-end code to a real back-end. We can't do that as long as we are directly referencing and creating a mock API service.

There are many solutions for the problem that we are facing. We will explore making a factory to decide what back-end to provide for us. A factory will also allow us to treat the mock API as a singleton. Treating the service as a singleton will allow us to prepopulate the service as part of the test setup.

In the services folder, let's create a new set of tests for creating the factory class and functionality.

import { expect } from 'chai';
import { ServiceFactory as factory } from './serviceFactory';

describe('Service Factory', () => {
it('exits', () => {
expect(factory).to.exist;
});
});

All we need to make this test pass is a class definition.

export class ServiceFactory {
}

Now we want a method to create a speaker service. Add a new describe to the factory tests.

describe('Create Speaker Service', () => {
it('exists', () => {
expect(factory.createSpeakerService).to.exist;
});
});

Notice the way we are using the factory, we are not initializing it. We want the factory to be a class with static methods. Having static functions will give us the singleton ability we want.

static createSpeakerService() {
}

Next up, we want to ensure that the createSpeakerService factory method will provide us with an instance of the mock API.

it('returns a speaker service', () => {
// act
let result = factory.createSpeakerService();

// assert
expect(result).to.be.an.instanceof(MockSpeakerService);
});

Making this test pass is easy, just return a new mock speaker service from the factory method.

static createSpeakerService() {
return new MockSpeakerService();
}

This isn't a singleton though. So, we still have some more work to do here. Let's write one more test in the factory before we swap out all the service calls in the application for factory calls. To verify that something is a singleton, we have to make sure it is the same throughout the application. We can do that by doing reference comparisons on successive calls. Another option is to create the speaker service, add a speaker to it, create a new speaker service, and try to pull the speaker from the second service. If we have done things correctly, the second option is the most thorough. We will do the first option here, but it would be a good exercise to do the second option on your own.

it('returns the same speaker service', () => {
// act
let service1 = factory.createSpeakerService();
let service2 = factory.createSpeakerService();

// assert
expect(service1).to.equal(service2);
});

To pass the test, we must ensure that the same instance of the speaker service is returned every time.

export default class ServiceFactory {
constructor() {
this._speakerService = null;
}

static createSpeakerService() {
return this._speakerService = this._speakerService ||
new MockSpeakerService();
}
}

The factory will now return the current value or create a new speaker service if the current value is null.

The next step is to go to each place where we are directly instantiating a mock speaker service and swap it out with a factory call. We will leave that as an exercise for you to do, but know that going forward we will assume that it has been done.

Now that we have the factory swapped out and it is generating a singleton, we can write the next action test. We want to test a successful retrieval of a speaker.

it('creates GET_SPEAKER_SUCCESS when speaker is found', () => {
// arrange
const speaker = {
id: 'test-speaker',
firstName: 'Test',
lastName: 'Speaker'
};
const store = mockStore({ speaker: {} });
const expectedActions = [
speakerActions.getSpeakerSuccess([speaker.id])
];
let service = factory.createSpeakerService();
service.create(speaker);

// act
return store.dispatch(
speakerActions.getSpeaker('test-speaker')).then(() => {
const actions = store.getActions();

// assert
expect(actions[0].type).to.equal(types.GET_SPEAKER_SUCCESS);
expect(actions[0].speaker.id).to.equal('test-speaker');
expect(actions[0].speaker.firstName).to.equal('Test');
expect(actions[0].speaker.lastName).to.equal('Speaker');
});
});

There is a lot going on in this test; let's walk through it. First in the arrange, we create a speaker object to be placed in the service, and used for the assertions. Next, still in the arrange, we create and configure the mock store. Lastly, in the arrange, we create the speaker service and we create our test speaker using the service.

Next, in the act, we dispatch a call to get the test speaker. Remember, this call is asynchronous. So, we must subscribe to then.

When the promise is resolved, we store the actions in a variable and assert that the first action has the correct type and payload.

Now to make this test pass we need to make some modifications to the getById method on the service.

export function getSpeaker(speakerId) {
return function(dispatch) {
return factory.createSpeakerService().getById(speakerId).then(
speaker => {
dispatch(getSpeakerSuccess(speaker));
}).catch(err => {
dispatch(getSpeakerFailure(err));
});
};
}

All we have really done here is add a then to handle the resolving of the promise. We now have, for all current intents and purposes, a working speaker service. Let's move on to creating the reducers for handling the get speaker actions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.179.161