Last active
March 29, 2017 14:22
-
-
Save damon-kreft/c40ea4b8a95aed1f23bf817bf41b4442 to your computer and use it in GitHub Desktop.
Unit Testing Using Dependency Injection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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)); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
}); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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