Skip to content

Instantly share code, notes, and snippets.

@damon-kreft
Last active March 29, 2017 14:22
Show Gist options
  • Save damon-kreft/c40ea4b8a95aed1f23bf817bf41b4442 to your computer and use it in GitHub Desktop.
Save damon-kreft/c40ea4b8a95aed1f23bf817bf41b4442 to your computer and use it in GitHub Desktop.
Unit Testing Using Dependency Injection
// Super simple data layer for fetching raw product data from the server
export const defaultOptions = {
headers: {
'Content-Type': 'application/json'
}
};
const mergeOptions = (...options) => {
return Object.assign({}, defaultOptions, ...options);
};
export const getProduct = (fetch, id, options = {}) => { // we explicitly pass fetch on every call
return fetch(`http://localhost/product/${id}`, mergeOptions(options));
};
export const getProducts = (fetch, options = {}) => {
return fetch('http://localhost/products', mergeOptions(options));
};
export const saveProduct = (fetch, id, data, options = {}) => {
return fetch(`http://localhost/product/${id}`, mergeOptions({
method: 'post',
body: JSON.stringify(data)
}, options));
};
// A test that mocks passes the mocked isomorphic-fetch to each method that uses it
import {
getProduct,
getProducts,
defaultOptions
} from './products';
import fetchMock from 'isomorphicFetchMock.js';
describe('products.js', () => {
it('getProduct()', (cb) => {
const expected = {
url: 'http://localhost/product/1',
options: Object.assign({}, defaultOptions, {
testy: 'westy'
})
};
getProduct(fetchMock, 1, { test: 'option' }).then((result) => { // <- fetch mock is passed to the method
expect(result).toEqual(expected);
cb();
});
});
it('getProducts()', (cb) => {
const expected = {
url: 'http://localhost/products',
options: Object.assign({}, defaultOptions, { test: 'option' })
};
getProducts(fetchMock, { test: 'option' }).then((result) => {
expect(result).toEqual(expected);
cb();
});
});
it('saveProduct()', (cb) => {
const expected = {
url: 'http://localhost/products',
options: Object.assign({}, defaultOptions, {
test: 'option',
body: JSON.stringify({ test: 'data' })
})
};
saveProduct(fetchMock, id, { test: 'data' }, { test: 'option' }).then((result) => {
expect(result).toEqual(expected);
cb();
});
});
});
// A simple isomorphic-fetch mock that returns what you pass into it so we can assert that the methods in products.js
// are passing the correct values (I'd add rejection logic in here too to test those cases but this is just for demo purposes)
const fetch = (url, options) => {
return Promise.resolve({url, options});
};
export default fetch;
import { ProductModel } from '../models/product';
import { saveProduct } from './products';
// This logic assumes our front-end deals exlusively with model classes (like with any traditional ORM) rather than raw server
// data, which is fickle. ProductModel is a class which takes in raw server data for a single product and converts to a fixed,
// predictable class object based off a model definition defined elsewhere. Our front-end then works directly with these data
// models as opposed to raw sever data.
export const saveProductModel(fetch, productModelToSave) { // we have to explicitly pass fetch on every call
const rawData = productModelToSave.toRawObject();
return saveProduct(fetch, productModelToSave.id, { body: rawData }).then((result) => { // <- fetch has to be passed in
return new ProductModel(result); // converts the raw server data returned from the save into a consistent Product model
});
};
// How can we test this file? 'fetch' has to be imported somewhere. If we can't mock this import, then fetch will be passed
// all the way down to 'productsData.js' triggering an actual data call which is obviously undesirable. If we somehow move
// the fetch out of here to somewhere else... we're just moving the problem elsewhere and that code will be untestable.
import fetch from 'isomorphic-fetch';
import { saveProductModel } from './productsBusinessLogic';
const SAVING_PRODUCT = 'SAVING_PRODUCT';
const SAVED_PRODUCT = 'SAVED_PRODUCT';
export const saveProductAction(productModelToSave) => {
return dispatch => {
dispatch({ type: SAVING_PRODUCT });
saveProductModel(fetch, productModelToSave).then((returnedProductModel) => {
dispatch({
type: SAVED_PRODUCT,
productModel: returnedProductModel
});
});
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment