I have an ember service thats primary concern is to fetch data for a specific model and the descendants of the model. The reason I am using this in a service is because the route for this particular type is using a slug which is not the primary key and therefore needs to do a store.query instead of store.find. When we fetch this model I have some logic that peeks the ember store to see if we can load it from there before going to the api query. Also this vendor is watching for the slug change and updating the current model based on that.
The problem I am having is that this seems to have very little documentation when it comes to how to test a thing like this. In fact I don't see a section on testing services anywhere in the guides here http://guides.emberjs.com/v2.1.0/
This is a snippet of the service in question.
import Ember from 'ember';
export default Ember.Service.extend({
_vendorSlug: null,
vendor: null,
vendorSlug: function (key, value) {
if (arguments.length > 1) {
if (this._vendorSlug) {
return this._vendorSlug;
}
this._vendorSlug = value;
}
return this._vendorSlug;
}.property(),
ensureVendorLoaded: function (slug) {
var service = this,
vendorSlug = slug || service.get('vendorSlug'),
currentVendor = service.get('vendor'),
storedVendor;
if (!Ember.isNone(currentVendor) && (vendorSlug === currentVendor.get('slug'))) {
return new Ember.RSVP.Promise((resolve) => {
resolve(currentVendor);
});
} else {
var storedVendors = service.store.peekAll('vendor').filter((vendor) => {
return vendor.get('slug') === vendorSlug;
});
if (storedVendors.length) {
storedVendor = storedVendors[0];
}
}
if (!Ember.isNone(storedVendor)) {
service.set('vendorSlug', storedVendor.get('slug'));
return new Ember.RSVP.Promise((resolve) => {
resolve(storedVendor);
});
}
return service.store.queryRecord('vendor', {slug: vendorSlug}).then((vendor) => {
service.set('vendor', vendor);
service.set('vendorSlug', vendor.get('slug'));
return vendor;
});
},
_vendorSlugChanged: function () {
if (this.get("vendorSlug") === this.get("vendor.slug")) {
return;
}
this.ensureVendorLoaded();
}.observes('vendorSlug')
});
I would like to be able to assert a couple of scenarios here with the store interaction. Vendor already set, vendor loaded from the peek filter, and vendor loaded from query.
I think I have finally come to a reasonable conclusion. Let me share with you what I think may be the best way to approach unit testing services that rely on the store.
The answer really lies in the assumption we must make when writing unit tests. That is, everything outside of our logical unit should be considered to work properly and our units should be completely independent.
Thus, with a service relying on the store it is best to create a stub or mock (see this question to understand the difference between a mock and a stub) for the store. A stub for the store itself is quite simple. Something like this will do:
store: {
find: function() {
var mockedModel = Ember.Object.create({/*empty*/});
return mockedModel;
},
query: ...
}
If you prefer to use a mock instead you could do something like the following (i made this really fast so it might not work completely but its enough to get the idea across):
import Ember from 'ember';
class MockStore {
constructor() {
this.models = Ember.A([]);
}
createRecord(modelName, record) {
// add a save method to the record
record.save = () => {
return new Ember.RSVP.Promise((resolve) => {
resolve(true);
});
};
if (!this.models[modelName]) {
this.models[modelName] = Ember.A([]);
}
this.models[modelName].pushObject(record);
return record;
}
query(modelName, query) {
let self = this;
return new Ember.RSVP.Promise((resolve) => {
let model = self.models[modelName];
// find the models that match the query
let results = model.filter((item) => {
let result = true;
for (let prop in query) {
if (query.hasOwnProperty(prop)) {
if (!item[prop]) {
result = false;
}
else if (query[prop] !== item[prop]) {
result = false;
}
}
}
return result;
});
resolve(results);
});
}
}
export default MockStore;
Next all you have to do is to set the store property (or whatever your calling it) on your service to a new mock store instance when you run a test. I did this like so:
import Ember from 'ember';
import { moduleFor, test } from 'ember-qunit';
import MockStore from '../../helpers/mock-store';
let session;
let store;
const username = '';
const password = '';
moduleFor('service:authentication', 'Unit | Service | authentication', {
beforeEach() {
session = Ember.Object.create({});
store = new MockStore();
}
});
test('it should authenticate the user', function (assert) {
// this sets the store property of the service to the mock store
let authService = this.subject({session: session, store: store});
authService.authenticate(username, password).then(() => {
assert.ok(session.get('username'));
});
});
The documentation on testing these situations is definitely poor, so perhaps there is a better method, but this is what I will be rolling with for now. Also, if you check out the Discourse project, which uses ember, they follow a similar pattern to what I described here, but in a little more advanced manner.
I'm not sure this is the answer you want, but I'll give it a shot anyway. An Ember Service is not really much more than an Ember Object and if you're "unit testing" that Service, it should be in isolation of its dependencies (otherwise it wouldn't be a unit test).
From my understanding (and this could be wrong). If you want to test that service you need to replace the store with a mock implementation.
//tests/unit/services/my-service.js
test('some scenario', function(assert) {
let service = this.subject({
store: Ember.Object.create({
peekAll(modelName){
//Return array for this scenario
},
query(model, params){
//Return array for this scenario
}
});
});
assert.ok(service);
});
I also think this is why there's little documentation testing services.
One resource I recommend about services is this talk from the Chicago Ember Meetup
Related
I'm trying to use Ember observers with EmberJS Octane latest version (4.1.0), but it does not seem to work.
Here is what I'm trying to achieve :
export default class SummaryService extends Service {
#service store;
#service authentication;
#reads('authentication.userId') userId;
#tracked weekPosts;
#tracked monthPosts;
#observer('userId')
loadPosts() {
this._loadPosts(this.userId);
}
_loadPosts(userId) {
this.store.query('posts', { filter: { 'current-week': 1, 'user-id': userId } })
.then((posts) => {
this.set('weekPosts', posts);
});
this.store.query('posts', { filter: { 'current-month': 1, 'user-id': userId } })
.then((posts) => {
this.set('monthPosts', posts);
});
}
}
=> The syntax is invalid.
I also tried :
#observer('userId', function() {
this._loadPosts();
});
=> The observer is indeed called, but this is undefined.
I also tried :
init() {
super.init(...arguments);
this.addObserver('currentUserId', this, '_loadPosts');
}
=> But this one does not call any method (even with inline method definition).
Finally, my last attempt was to use #computed properties for weekPosts and monthPosts instead, like this :
export default class SummaryService extends Service {
/* ... */
#computed('userId')
get weekPosts() {
return this.store.query('posts', { filter: { 'current-week': 1 } })
.then((posts) => { return posts; });
}
}
=> But it always returns a Promise, so I can't call .reduce on it from a computed property used by a Component :
export default class SummaryComponent extends Component {
#computed('weekPosts')
get weekPostsViewsCount() {
return this.weekPosts.reduce((sum, post) => { return sum + post.viewCount });
}
}
I finally got something working pretty ugly using an ArrayProxy.extend(PromiseProxyMixin) returned by the weekPosts computed property, but I'm definitely not happy with this for the following reasons :
So much code for such a simple thing
Everything (component, template) which uses the weekPosts has to make sure the promise is fulfilled before working with it
The promise is an implementation detail of the service and should not be visible in any way out of it
Thanks !
Observers won't work for what you want to do -- since it looks like you want to reactively re-fetch data (using ember-data) based on when userId changes, I have a library suggestion:
https://github.com/NullVoxPopuli/ember-data-resources
With this library, we can replace most of your service with this:
import { query } from 'ember-data-resources';
export default class SummaryService extends Service {
#service authentication;
#reads('authentication.userId') userId;
_weekPosts = query(this, 'posts', () => ({
filter: { 'current-week': 1, 'user-id': this.userId
}));
_monthPosts = query(this, 'posts', () => ({
filter: { 'current-month': 1, 'user-id': this.userId
}));
get weekPosts() {
return this._weekPosts.records ?? [];
}
get monthPosts() {
return this._monthPosts.records ?? [];
}
get isLoading() {
return this._weekPosts.isLoading || this._monthPosts.isLoading;
}
}
The advantage here is that you also have the ability to manage error/loading/etc states.
This uses a technique / pattern called "Derived state", where instead of performing actions, or reacting to changes, or interacting withe lifecycles, you instead define how data is derived from other data.
In this case, we have known data, the userId, and we want to derive queries, using query from ember-data-resources, also uses derived state to provide the following api:
this._weekPosts
.records
.error
.isLoading
.isSuccess
.isError
.hasRun
Which then allows you to define other getters which derive data, weekPosts, isLoading, etc.
Derived state is much easier to debug than observer code -- and it's lazy, so if you don't access data/getters/etc, that data is not calculated.
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.
I am new to building websites and all I want to do at this stage is to use local JSON file to retrive data instead of mirage provided in ember tutorial. you have mirage/config.js like this:
export default function() {
this.namespace = '/api';
let rentals = [{
//JSON
}];
this.get('/rentals', function(db, request) {
if(request.queryParams.area !== undefined) {
let filteredRentals = rentals.filter(function(i) {
return i.attributes.area.toLowerCase().indexOf(request.queryParams.area.toLowerCase()) !== -1;
});
return { data: filteredRentals };
} else {
return { data: rentals };
}
});
// Find and return the provided rental from our rental list above
this.get('/rentals/:id', function (db, request) {
return { data: rentals.find((rental) => request.params.id === rental.id) };
});
}
This article shows part of the solution but I don't know where it's supposed to be written. Any help would be much appreciated.
There are a few different options for stubbing some data without using mirage. The clearest and easiest is fetch.
Put your json file in the public folder, let's call it something.json. Then, use fetch to get the data (this is the model hook of a route):
model() {
return fetch('something.json')
.then(function(res) {
return res.json()
})
}
This answer applies from at least 1.13 onward (and possibly earlier). It was written as of 3.1.
I'm using VueJS from Vue CLI. So all my components are in .vue format.
In one of my components, I have an array called fields in the data section.
//Component.vue
data() {
return {
fields : [{"name" : "foo", "title" : "Foosteria"}, {"name" : "bar", "title" : "Barrista"}]
}
}
I have a computed property that is a subset of fields
//Component.vue
computed : {
subsetOfFields () {
// Something else in component data determines this list
}
}
I've set up all of my unit tests in jasmine like this and they work fine.
//Component.spec.js
import Vue from 'vue'
import MyComponent from 'Component.vue'
describe("Component test", function() {
var myComponentVar = new Vue(MyComponent);
var vm = myComponentVar.$mount();
beforeEach(function() {
vm = myComponentVar.$mount();
);
afterEach(function() {
vm = myComponentVar.$destroy();
});
it("First spec tests something", function() {
...
});
});
For everything else, doing something inside the spec, then running assertions on the data objects works just fine. However, running an assertion on subsetOfFields always returns an empty array. Why so? What should I do, in order to be able to test it?
FYI, I even tried nesting the spec inside another describe block and then adding a beforeEach which initializes the fields array. It did not work.
However, initializing fields inside the generic beforeEach function worked. But I don't want to initialize the fields array with that mock data for the other specs.
I came across this link that talks about testing and the section you'll need to look at is the Vue.nextTick(...) section
https://alligator.io/vuejs/unit-testing-karma-mocha/
The block I'm talking about is below:
import Vue from 'vue';
// The path is relative to the project root.
import TestMe2 from 'src/components/TestMe2';
describe('TestMe2.vue', () => {
...
it(`should update when dataText is changed.`, done => {
const Constructor = Vue.extend(TestMe2);
const comp = new Constructor().$mount();
comp.dataProp = 'New Text';
Vue.nextTick(() => {
expect(comp.$el.textContent)
.to.equal('New Text');
// Since we're doing this asynchronously, we need to call done() to tell Mocha that we've finished the test.
done();
});
});
});
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 },
});
});
});