Security News
Bun 1.2 Released with 90% Node.js Compatibility and Built-in S3 Object Support
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.
ember-data-factory-guy
Advanced tools
Feel the thrill and enjoyment of testing when using Factories instead of Fixtures. Factories simplify the process of testing, making you more efficient and your tests more readable.
NEW starting with v3.8
NEW starting with v3.2.1
NEW You can use factory guy in ember-twiddle
NEW If using new style of ember-qunit acceptance tests with setupApplicationTest
check out demo here: user-view-test.js:
NEW starting with v2.13.27
attributesFor
NEW starting with v2.13.24
manualSetup(this)
NEW and Improved starting with v2.13.22
Older but still fun things
Why is FactoryGuy so awesome
Visit the EmberJS Community #e-factory-guy Slack channel
tests/factories
directoryember install ember-data-factory-guy
( ember-data-1.13.5+ )ember install ember-data-factory-guy@1.13.2
( ember-data-1.13.0 + )ember install ember-data-factory-guy@1.1.2
( ember-data-1.0.0-beta.19.1 )ember install ember-data-factory-guy@1.0.10
( ember-data-1.0.0-beta.16.1 )package.json
npm prune
ember install ember-data-factory-guy
( for the latest release )In the following examples, assume the models look like this:
// standard models
User = DS.Model.extend({
name: DS.attr('string'),
style: DS.attr('string'),
projects: DS.hasMany('project'),
hats: DS.hasMany('hat', {polymorphic: true})
});
Project = DS.Model.extend({
title: DS.attr('string'),
user: DS.belongsTo('user')
});
// polymorphic models
Hat = DS.Model.extend({
type: DS.attr('string'),
user: DS.belongsTo('user')
});
BigHat = Hat.extend();
SmallHat = Hat.extend();
User
, the factory name would be user
tests/factories
directory.ember generate factory user
This will create a factory in a file named user.js
in the tests/factories
directory.Sample full blown factory: user.js
Brief sample of a factory definition:
// file tests/factories/user.js
import FactoryGuy from 'ember-data-factory-guy';
FactoryGuy.define('user', {
// Put default 'user' attributes in the default section
default: {
style: 'normal',
name: 'Dude'
},
// Create a named 'user' with custom attributes
admin: {
style: 'super',
name: 'Admin'
}
});
type
and this is not a polymorphic model, use the option
polymorphic: false
in your definition// file: tests/factories/cat.js
FactoryGuy.define('cat', {
polymorphic: false, // manually flag this model as NOT polymorphic
default: {
// usually, an attribute named 'type' is for polymorphic models, but the defenition
// is set as NOT polymorphic, which allows this type to work as attibute
type: 'Cute',
name: (f)=> `Cat ${f.id}`
}
});
type
is used to hold the model name
// file tests/factories/small-hat.js
import FactoryGuy from 'ember-data-factory-guy';
FactoryGuy.define('small-hat', {
default: {
type: 'SmallHat'
}
})
// file tests/factories/big-hat.js
import FactoryGuy from 'ember-data-factory-guy';
FactoryGuy.define('big-hat', {
default: {
type: 'BigHat'
}
})
In other words, don't do this:
// file tests/factories/hat.js
import FactoryGuy from 'ember-data-factory-guy';
FactoryGuy.define('hat', {
default: {},
small-hat: {
type: 'SmallHat'
},
big-hat: {
type: 'BigHat'
}
})
sequences
hashFactoryGuy.generate
FactoryGuy.define('user', {
sequences: {
userName: (num)=> `User${num}`
},
default: {
// use the 'userName' sequence for this attribute
name: FactoryGuy.generate('userName')
}
});
let first = FactoryGuy.build('user');
first.get('name') // => 'User1'
let second = FactoryGuy.make('user');
second.get('name') // => 'User2'
FactoryGuy.define('project', {
special_project: {
title: FactoryGuy.generate((num)=> `Project #${num}`)
},
});
let json = FactoryGuy.build('special_project');
json.get('title') // => 'Project #1'
let project = FactoryGuy.make('special_project');
project.get('title') // => 'Project #2'
FactoryGuy.define('user', {
default: {
// Don't need the userName sequence, since the id is almost
// always a sequential number, and you can use that.
// f is the fixture being built as the moment for this factory
// definition, which has the id available
name: (f)=> `User${f.id}`
},
traits: {
boring: {
style: (f)=> `${f.id} boring`
},
funny: {
style: (f)=> `funny ${f.name}`
}
}
});
let json = FactoryGuy.build('user', 'funny');
json.get('name') // => 'User1'
json.get('style') // => 'funny User1'
let user = FactoryGuy.make('user', 'boring');
user.get('id') // => 2
user.get('style') // => '2 boring'
Note the style attribute was built from a function which depends on the name and the name is a generated attribute from a sequence function
attributesFor , build/buildList , make/makeList
FactoryGuy.define('user', {
traits: {
big: { name: 'Big Guy' },
friendly: { style: 'Friendly' },
bfg: { name: 'Big Friendly Giant', style: 'Friendly' }
}
});
let user = FactoryGuy.make('user', 'big', 'friendly');
user.get('name') // => 'Big Guy'
user.get('style') // => 'Friendly'
let giant = FactoryGuy.make('user', 'big', 'bfg');
user.get('name') // => 'Big Friendly Giant' - name defined in the 'bfg' trait overrides the name defined in the 'big' trait
user.get('style') // => 'Friendly'
You can still pass in a hash of options when using traits. This hash of attributes will override any trait attributes or default attributes
let user = FactoryGuy.make('user', 'big', 'friendly', {name: 'Dave'});
user.get('name') // => 'Dave'
user.get('style') // => 'Friendly'
import FactoryGuy from 'ember-data-factory-guy';
FactoryGuy.define("project", {
default: {
title: (f) => `Project ${f.id}`
},
traits: {
// this trait is a function
// note that the fixure is passed in that will have
// default attributes like id at a minimum and in this
// case also a title ( `Project 1` ) which is default
medium: (f) => {
f.title = `Medium Project ${f.id}`
},
goofy: (f) => {
f.title = `Goofy ${f.title}`
}
withUser: (f) => {
// NOTE: you're not using FactoryGuy.belongsTo as you would
// normally in a fixture definition
f.user = FactoryGuy.make('user')
}
}
});
So, when you make / build a project like:
let project = make('project', 'medium');
project.get('title'); //=> 'Medium Project 1'
let project2 = build('project', 'goofy');
project2.get('title'); //=> 'Goofy Project 2'
let project3 = build('project', 'withUser');
project3.get('user.name'); //=> 'User 1'
Your trait function assigns the title as you described in the function
Can setup belongsTo or hasMany associations in factory definitions
Can setup belongsTo or hasMany associations manually
FactoryGuy.build
/FactoryGuy.buildList
and FactoryGuy.make
/FactoryGuy.makeList
build
and make
- you either build
JSON in every levels of associations or make
objects. build
is taking serializer into account for every model which means that output from build
might be different than expected input defined in factory in make
.Special tips for links
FactoryGuy.define('project', {
traits: {
withUser: { user: {} },
withAdmin: { user: FactoryGuy.belongsTo('user', 'admin') },
withManagerLink(f) { // f is the fixture being created
f.links = {manager: `/projects/${f.id}/manager`}
}
}
});
let user = make('project', 'withUser');
project.get('user').toJSON({includeId: true}) // => {id:1, name: 'Dude', style: 'normal'}
user = make('user', 'withManagerLink');
user.belongsTo('manager').link(); // => "/projects/1/manager"
See FactoryGuy.build
/FactoryGuy.buildList
for more ideas
let user = make('user');
let project = make('project', {user});
project.get('user').toJSON({includeId: true}) // => {id:1, name: 'Dude', style: 'normal'}
Note that though you are setting the 'user' belongsTo association on a project, the reverse user hasMany 'projects' association is being setup for you on the user ( for both manual and factory defined belongsTo associations ) as well
user.get('projects.length') // => 1
hasMany
records via the default
section of the factory definition. Prefer traits to set up such associations. Creating them via the default
section is known to cause some undefined behavior when using the makeNew
API.
FactoryGuy.define('user', {
traits: {
withProjects: {
projects: FactoryGuy.hasMany('project', 2)
},
withPropertiesLink(f) { // f is the fixture being created
f.links = {properties: `/users/${f.id}/properties`}
}
}
});
let user = make('user', 'withProjects');
user.get('projects.length') // => 2
user = make('user', 'withPropertiesLink');
user.hasMany('properties').link(); // => "/users/1/properties"
You could also setup a custom named user definition:
FactoryGuy.define('user', {
userWithProjects: { projects: FactoryGuy.hasMany('project', 2) }
});
let user = make('userWithProjects');
user.get('projects.length') // => 2
See FactoryGuy.build
/FactoryGuy.makeList
for more ideas
let project1 = make('project');
let project2 = make('project');
let user = make('user', {projects: [project1, project2]});
user.get('projects.length') // => 2
// or
let projects = makeList('project', 2);
let user = make('user', {projects});
user.get('projects.length') // => 2
Note that though you are setting the 'projects' hasMany association on a user, the reverse 'user' belongsTo association is being setup for you on the project ( for both manual and factory defined hasMany associations ) as well
projects.get('firstObject.user') // => user
FactoryGuy.define('user', {
traits: {
withCompanyLink(f): {
// since you can assign many different links with different traits,
// you should Object.assign so that you add to the links hash rather
// than set it directly ( assuming you want to use this feature )
f.links = Object.assign({company: `/users/${f.id}/company`}, f.links);
},
withPropertiesLink(f) {
f.links = Object.assign({properties: `/users/${f.id}/properties`}, f.links);
}
}
});
// setting links with traits
let company = make('company')
let user = make('user', 'withCompanyLink', 'withPropertiesLink', {company});
user.hasMany('properties').link(); // => "/users/1/properties"
user.belongsTo('company').link(); // => "/users/1/company"
// the company async relationship has a company AND link to fetch it again
// when you reload that relationship
user.get('company.content') // => company
user.belongsTo('company').reload() // would use that link "/users/1/company" to reload company
// you can also set traits with your build/buildList/make/makeList options
user = make('user', {links: {properties: '/users/1/properties'}});
There is a sample Factory using inheritance here: big-group.js
make
/makeList
/build
/buildList
Let's say you have a model and a factory like this:
// app/models/dog.js
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
export default Model.extend({
dogNumber: attr('string'),
sound: attr('string')
});
// tests/factories/dog.js
import FactoryGuy from 'ember-data-factory-guy';
const defaultVolume = "Normal";
FactoryGuy.define('dog', {
default: {
dogNumber: (f)=> `Dog${f.id}`,
sound: (f) => `${f.volume || defaultVolume} Woof`
},
});
Then to build the fixture:
let dog2 = build('dog', { volume: 'Soft' });
dog2.get('sound'); //=> `Soft Woof`
afterMake
afterMake
is called.
afterMake
to finish by the
time onload
is called.afterMake
Assuming the factory-guy model definition defines afterMake
function:
FactoryGuy.define('property', {
default: {
name: 'Silly property'
},
// optionally set transient attributes, that will be passed in to afterMake function
transient: {
for_sale: true
},
// The attributes passed to after make will include any optional attributes you
// passed in to make, and the transient attributes defined in this definition
afterMake: function(model, attributes) {
if (attributes.for_sale) {
model.set('name', model.get('name') + '(FOR SALE)');
}
}
}
You would use this to make models like:
run(function () {
let property = FactoryGuy.make('property');
property.get('name'); // => 'Silly property(FOR SALE)')
let property = FactoryGuy.make('property', {for_sale: false});
property.get('name'); // => 'Silly property')
});
Remember to import the run
function with import { run } from "@ember/runloop"
;
FactoryGuy.attributesFor
FactoryGuy.make
FactoryGuy.makeNew
FactoryGuy.makeList
FactoryGuy.build
FactoryGuy.buildList
build
/buildList
or make
/makeList
build
/buildList
or make
/makeList
FactoryGuy.attributesFor
import { attributesFor } from 'ember-data-factory-guy';
// make a user with certain traits and options
attributesFor('user', 'silly', {name: 'Fred'}); // => { name: 'Fred', style: 'silly'}
FactoryGuy.make
FactoryGuy.make
/FactoryGuy.makeList
import { make } from 'ember-data-factory-guy';
// make a user with the default attributes in user factory
let user = make('user');
user.toJSON({includeId: true}); // => {id: 1, name: 'User1', style: 'normal'}
// make a user with the default attributes plus those defined as 'admin' in the user factory
let user = make('admin');
user.toJSON({includeId: true}); // => {id: 2, name: 'Admin', style: 'super'}
// make a user with the default attributes plus these extra attributes provided in the optional hash
let user = make('user', {name: 'Fred'});
user.toJSON({includeId: true}); // => {id: 3, name: 'Fred', style: 'normal'}
// make an 'admin' user with these extra attributes
let user = make('admin', {name: 'Fred'});
user.toJSON({includeId: true}); // => {id: 4, name: 'Fred', style: 'super'}
// make a user with a trait ('silly') plus these extra attributes provided in the optional hash
let user = make('user', 'silly', {name: 'Fred'});
user.toJSON({includeId: true}); // => {id: 5, name: 'Fred', style: 'silly'}
// make a user with a hats relationship ( hasMany ) composed of pre-made hats
let hat1 = make('big-hat');
let hat2 = make('big-hat');
let user = make('user', {hats: [hat1, hat2]});
user.toJSON({includeId: true})
// => {id: 6, name: 'User2', style: 'normal', hats: [{id:1, type:"big_hat"},{id:1, type:"big_hat"}]}
// note that hats are polymorphic. if they weren't, the hats array would be a list of ids: [1,2]
// make a user with a company relationship ( belongsTo ) composed of a pre-made company
let company = make('company');
let user = make('user', {company: company});
user.toJSON({includeId: true}) // => {id: 7, name: 'User3', style: 'normal', company: 1}
// make user with links to async hasMany properties
let user = make('user', {properties: {links: '/users/1/properties'}});
// make user with links to async belongsTo company
let user = make('user', {company: {links: '/users/1/company'}});
// for model fragments you get an object
let object = make('name'); // => {firstName: 'Boba', lastName: 'Fett'}
FactoryGuy.makeNew
FactoryGuy.make
FactoryGuy.makeList
Usage:
import { make, makeList } from 'ember-data-factory-guy';
// Let's say bob is a named type in the user factory
makeList('user', 'bob') // makes 0 bob's
makeList('user', 'bob', 2) // makes 2 bob's
makeList('user', 'bob', 2, 'with_car', {name: "Dude"})
// makes 2 bob users with the 'with_car' trait and name of "Dude"
// In other words, applies the traits and options to every bob made
makeList('user', 'bob', 'with_car', ['with_car', {name: "Dude"}])
// makes 2 users with bob attributes. The first also has the 'with_car' trait and the
// second has the 'with_car' trait and name of "Dude", so you get 2 different users
FactoryGuy.build
FactoryGuy.make
FactoryGuy.build
/FactoryGuy.buildList
payloadsget
methodadd
method
add()
methodUsage:
import { build, buildList } from 'ember-data-factory-guy';
// build a basic user with the default attributes from the user factory
let json = build('user');
json.get() // => {id: 1, name: 'User1', style: 'normal'}
// build a user with the default attributes plus those defined as 'admin' in the user factory
let json = build('admin');
json.get() // => {id: 2, name: 'Admin', style: 'super'}
// build a user with the default attributes with extra attributes
let json = build('user', {name: 'Fred'});
json.get() // => {id: 3, name: 'Fred', style: 'normal'}
// build the admin defined user with extra attributes
let json = build('admin', {name: 'Fred'});
json.get() // => {id: 4, name: 'Fred', style: 'super'}
// build default user with traits and with extra attributes
let json = build('user', 'silly', {name: 'Fred'});
json.get() // => {id: 5, name: 'Fred', style: 'silly'}
// build user with hats relationship ( hasMany ) composed of a few pre 'built' hats
let hat1 = build('big-hat');
let hat2 = build('big-hat');
let json = build('user', {hats: [hat1, hat2]});
// note that hats are polymorphic. if they weren't, the hats array would be a list of ids: [1,2]
json.get() // => {id: 6, name: 'User2', style: 'normal', hats: [{id:1, type:"big_hat"},{id:1, type:"big_hat"}]}
// build user with company relationship ( belongsTo ) composed of a pre 'built' company
let company = build('company');
let json = build('user', {company});
json.get() // => {id: 7, name: 'User3', style: 'normal', company: 1}
// build and compose relationships to unlimited degree
let company1 = build('company', {name: 'A Corp'});
let company2 = build('company', {name: 'B Corp'});
let owners = buildList('user', { company:company1 }, { company:company2 });
let buildJson = build('property', { owners });
// build user with links to async hasMany properties
let user = build('user', {properties: {links: '/users/1/properties'}});
// build user with links to async belongsTo company
let user = build('user', {company: {links: '/users/1/company'}});
let json = build('user', 'with_company', 'with_hats');
json // =>
{
user: {
id: 1,
name: 'User1',
company: 1,
hats: [
{type: 'big_hat', id:1},
{type: 'big_hat', id:2}
]
},
companies: [
{id: 1, name: 'Silly corp'}
],
'big-hats': [
{id: 1, type: "BigHat" },
{id: 2, type: "BigHat" }
]
}
FactoryGuy.buildList
FactoryGuy.makeList
build
/buildList
payloadsget()
method
get(index)
to get an individual item from the listadd
method
.add(payload)
.add({meta})
Usage:
import { build, buildList } from 'ember-data-factory-guy';
let bobs = buildList('bob', 2); // builds 2 Bob's
let bobs = buildList('bob', 2, {name: 'Rob'}); // builds 2 Bob's with name of 'Rob'
// builds 2 users, one with name 'Bob' , the next with name 'Rob'
let users = buildList('user', { name:'Bob' }, { name:'Rob' });
// builds 2 users, one with 'boblike' and the next with name 'adminlike' features
// NOTE: you don't say how many to make, because each trait is making new user
let users = buildList('user', 'boblike', 'adminlike');
// builds 2 users:
// one 'boblike' with stoner style
// and the next 'adminlike' with square style
// NOTE: how you are grouping traits and attributes for each one by wrapping them in array
let users = buildList('user', ['boblike', { style: 'stoner' }], ['adminlike', {style: 'square'}]);
add()
methodbuild('user').add({json: batMan})
build('user').add(batMan)
Usage:
let batMan = build('bat_man');
let userPayload = build('user').add(batMan);
userPayload = {
user: {
id: 1,
name: 'User1',
style: "normal"
},
'super-heros': [
{
id: 1,
name: "BatMan",
type: "SuperHero"
}
]
};
Usage:
let json1 = buildList('profile', 2).add({ meta: { previous: '/profiles?page=1', next: '/profiles?page=3' } });
let json2 = buildList('profile', 2).add({ meta: { previous: '/profiles?page=2', next: '/profiles?page=4' } });
mockQuery('profile', {page: 2}).returns({ json: json1 });
mockQuery('profile', {page: 3}).returns({ json: json2 });
store.query('profile', {page: 2}).then((records)=> // first 2 from json1
store.query('profile', {page: 3}).then((records)=> // second 2 from json2
get()
methodget()
returns all attributes of top level modelget(attribute)
gives you an attribute from the top level modelget(index)
gives you the info for a hasMany relationship at that indexget(relationships)
gives you just the id or type ( if polymorphic )
let json = build('user');
json.get() //=> {id: 1, name: 'User1', style: 'normal'}
json.get('id') // => 1
let json = buildList('user', 2);
json.get(0) //=> {id: 1, name: 'User1', style: 'normal'}
json.get(1) //=> {id: 2, name: 'User2', style: 'normal'}
let json = buildList('user', 'boblike', 'adminlike');
json.get(0) //=> {id: 1, name: 'Bob', style: 'boblike'}
json.get(1) //=> {id: 2, name: 'Admin', style: 'super'}
let json = build('user', 'with_company', 'with_hats');
json.get() //=> {id: 1, name: 'User1', style: 'normal'}
// to get hats (hasMany relationship) info
json.get('hats') //=> [{id: 1, type: "big_hat"},{id: 1, type: "big_hat"}]
// to get company ( belongsTo relationship ) info
json.get('company') //=> {id: 1, type: "company"}
let company = build('company');
let hats = buildList('big-hats');
let user = build('user', {company , hats});
user.get() //=> {id: 1, name: 'User1', style: 'normal'}
// to get hats info from hats json
hats.get(0) //=> {id: 1, type: "BigHat", plus .. any other attributes}
hats.get(1) //=> {id: 2, type: "BigHat", plus .. any other attributes}
// to get company info
company.get() //=> {id: 1, type: "Company", name: "Silly corp"}
You can set up scenarios for your app that use all your factories from tests updating config/environment.js
.
NOTE: Do not use settings in the test
environment. Factories are enabled
by default for the test
environment and setting the flag tells factory-guy to load the app/scenarios
files which are not needed for using factory-guy in testing. This will result in errors being generated if
the app/scenarios files do not exist.
// file: config/environment.js
// in development you don't have to set enabled to true since that is default
if (environment === 'development') {
ENV.factoryGuy = { useScenarios: true };
ENV.locationType = 'auto';
ENV.rootURL = '/';
}
// or
if (environment === 'production') {
ENV.factoryGuy = {enabled: true, useScenarios: true};
ENV.locationType = 'auto';
ENV.rootURL = '/';
}
Place your scenarios in the app/scenarios
directory
scenarios/main.js
file since this is the starting pointScenario
class // file: app/scenarios/main.js
import {Scenario} from 'ember-data-factory-guy';
import Users from './users';
// Just for fun, set the log level ( to 1 ) and see all FactoryGuy response info in console
Scenario.settings({
logLevel: 1, // 1 is the max for now, default is 0
});
export default class extends Scenario {
run() {
this.include([Users]); // include other scenarios
this.mockFindAll('products', 3); // mock some finds
this.mock({
type: 'POST',
url: '/api/v1/users/sign_in',
responseText: { token:"0123456789-ab" }
}); // mock a custom endpoint
}
}
// file: app/scenarios/users.js
import {Scenario} from 'ember-data-factory-guy';
export default class extends Scenario {
run() {
this.mockFindAll('user', 'boblike', 'normal');
this.mockDelete('user');
}
}
As of 2.5.2 you can create factories which contain ember-data-model-fragments. Setting up your fragments is easy and follows the same process as setting up regular factories. The mapping between fragment types and their associations are like so:
Fragment Type | Association |
---|---|
fragment | FactoryGuy.belongsTo |
fragmentArray | FactoryGuy.hasMany |
array | [] |
For example, say we have the following Employee
model which makes use of the fragment
, fragmentArray
and array
fragment types.
// Employee model
export default Model.extend({
name: fragment('name'),
phoneNumbers: fragmentArray('phone-number')
})
// Name fragment
export default Fragment.extend({
titles: array('string'),
firstName: attr('string'),
lastName: attr('string')
});
// Phone Number fragment
export default Fragment.extend({
number: attr('string')
type: attr('string')
});
A factory for this model and its fragments would look like so:
// Employee factory
FactoryGuy.define('employee', {
default: {
name: FactoryGuy.belongsTo('name'), //fragment
phoneNumbers: FactoryGuy.hasMany('phone-number') //fragmentArray
}
});
// Name fragment factory
FactoryGuy.define('name', {
default: {
titles: ['Mr.', 'Dr.'], //array
firstName: 'Jon',
lastName: 'Snow'
}
});
// Phone number fragment factory
FactoryGuy.define('phone-number', {
default: {
number: '123-456-789',
type: 'home'
}
});
To set up associations manually ( and not necessarily in a factory ), you should do:
let phoneNumbers = makeList('phone-numbers', 2);
let employee = make('employee', { phoneNumbers });
// OR
let phoneNumbers = buildList('phone-numbers', 2).get();
let employee = build('employee', { phoneNumbers }).get();
For a more detailed example of setting up fragments have a look at:
If you are making an addon with factories and you want the factories available to Ember apps using your addon, place the factories in test-support/factories
instead of tests/factories
. They should be available both within your addon and in Ember apps that use your addon.
DRFSerializer
so all relationships should either
DS.EmbeddedRecordsMixin
if you want to use build
/buildList
make
/makeList
and in your mocks, and return models instead of json: let projects = makeList('projects', 2); // put projects in the store
let user = make('user', { projects }); // attach them to user
mockFindRecord('user').returns({model: user}); // now the mock will return a user that has projects
fails()
with errors hash is not working reliably
mockWhatever(args).fails()
FactoryGuy handles JSON-API / RESTSerializer / JSONSerializer out of the box.
In case your API doesn't follow any of these conventions, you can still make a custom fixture builder
or modify the FixtureConverters
and JSONPayload
classes that exist.
FactoryGuy.cacheOnlyMode
findRecord
) you have to put something in the storefindAll
) you don't have to put anything in the storeexcept
parameter as a list of models you don't want to cache
This is helpful, when:
make
/makeList
, and then prevent
calls like store.findRecord
or store.findAll
from fetching more data, since you have
already setup the store with make
/makeList
data.Usage:
import FactoryGuy, { makeList } from 'ember-data-factory-guy';
import moduleForAcceptance from '../helpers/module-for-acceptance';
moduleForAcceptance('Acceptance | Profiles View');
test("Using FactoryGuy.cacheOnlyMode", async function() {
FactoryGuy.cacheOnlyMode();
// the store.findRecord call for the user will go out unless there is a user
// in the store
make('user', {name: 'current'});
// the application starts up and makes calls to findAll a few things, but
// those can be ignored because of the cacheOnlyMode
// for this test I care about just testing profiles
makeList("profile", 2);
await visit('/profiles');
// test stuff
});
test("Using FactoryGuy.cacheOnlyMode with except", async function() {
FactoryGuy.cacheOnlyMode({except: ['profile']});
make('user', {name: 'current'});
// this time I want to allow the ajax call so I can return built json payload
mockFindAll("profile", 2);
await visit('/profiles');
// test stuff
});
FactoryGuy needs to setup the factories before the test run.
By default, you only need to call manualSetup(this)
in unit/component/acceptance tests
Or you can use the new setupFactoryGuy(hooks) method if your using the new qunit style tests
import { setupFactoryGuy } from "ember-data-factory-guy";
module('Acceptance | User View', function(hooks) {
setupApplicationTest(hooks);
setupFactoryGuy(hooks);
test("blah blah", async function(assert) {
await visit('work');
assert.ok('bah was spoken');
});
});
Sample model test: profile-test.js
moduleForModel
( ember-qunit ), or describeModel
( ember-mocha ) test helperSample component test: single-user-test.js
moduleForComponent
( ember-qunit ), or describeComponent
( ember-mocha ) helperimport { make, manualSetup } from 'ember-data-factory-guy';
import hbs from 'htmlbars-inline-precompile';
import { test, moduleForComponent } from 'ember-qunit';
moduleForComponent('single-user', 'Integration | Component | single-user (manual setup)', {
integration: true,
beforeEach: function () {
manualSetup(this);
}
});
test("shows user information", function () {
let user = make('user', {name: 'Rob'});
this.render(hbs`{{single-user user=user}}`);
this.set('user', user);
ok(this.$('.name').text().match(user.get('name')));
ok(this.$('.funny-name').text().match(user.get('funnyName')));
});
setupApplicationTest
check out demo here: user-view-test.js:Uses pretender
http GET mocks
returns()
for setting the payload response
returns()
accepts parameters like: json, model, models, id, ids, headers
let mock = mockFindAll('user').returns({headers: {'X-Man': "Wolverine"});
mock.returns({headers: {'X-Weapon': "Claws"}});
http POST/PUT/DELETE
Custom mocks (http GET/POST/PUT/DELETE)
Use method fails()
to simulate failure
Use method succeeds()
to simulate success
fails()
and you want to set the
mock to succeed to simulate a successful retryUse property timesCalled
to verify how many times the ajax call was mocked
mockQuery
, mockQueryRecord
, mockFindAll
, mockReload
, or mockUpdate
mockFindRecord
will always be at most 1 since it will only make ajax call
the first time, and then the store will use cache the second time const mock = mockQueryRecord('company', {}).returns({ json: build('company') });
FactoryGuy.store.queryRecord('company', {}).then(()=> {
FactoryGuy.store.queryRecord('company', {}).then(()=> {
mock.timesCalled //=> 2
});
});
Use method disable()
to temporarily disable the mock. You can re-enable
the disabled mock using enable()
.
Use method destroy()
to completely remove the mock handler for the mock.
The isDestroyed
property is set to true
when the mock is destroyed.
FactoryGuy.settings({logLevel: 1, responseTime: 1000});
Usable on all mocks
Use optional object arguments status and response and convertErrors to customize
{errors: {name: "Name too short"}}
Examples:
let errors401 = {errors: {description: "Unauthorized"}};
let mock = mockFindAll('user').fails({status: 401, response: errors401});
let errors422 = {errors: {name: "Name too short"}};
let mock = mockFindRecord('profile').fails({status: 422, response: errors422});
let errorsMine = {errors: [{detail: "Name too short", title: "I am short"}]};
let mock = mockFindRecord('profile').fails({status: 422, response: errorsMine, convertErrors: false});
mockFindRecord
store.findRecord('modelType', id)
make
or build
mockFindRecord
( fixture or model name, optional traits, optional attributes object)returns()
for controlling the response payload
adapterOptions()
for setting adapterOptions ( get passed to urlForFindRecord )mockFindRecord
: user-view-test.js:Usage:
import { build, make, mockFindRecord } from 'ember-data-factory-guy';
// mockFindRecord automatically returns json for the modelType ( in this case 'user' )
let mock = mockFindRecord('user');
let userId = mock.get('id');
returns({json})
to return json object let user = build('user', 'whacky', {isDude: true});
let mock = mockFindRecord('user').returns({ json: user });
// user.get('id') => 1
// user.get('style') => 'whacky'
// or to acccomplish the same thing with less code
let mock = mockFindRecord('user', 'whacky', {isDude: true});
// mock.get('id') => 1
// mock.get('style') => 'whacky'
let user = mock.get();
// user.id => 1
// user.style => 'whacky'
returns({model})
to return model instance let user = make('user', 'whacky', {isDude: false});
let mock = mockFindRecord('user').returns({ model: user });
// user.get('id') => 1
// you can now also user.get('any-computed-property')
// since you have a real model instance
let user = make('user', 'whacky', {isDude: false});
let mock = mockFindRecord(user);
// user.get('id') === mock.get('id')
// basically a shortcut to the above .returns({ model: user })
// as this sets up the returns for you
let user2 = build('user', {style: "boring"});
mock.returns({ json: user2 });
// mock.get('id') => 2
fails
method mockFindRecord('user').fails();
let profile = make('profile');
mockFindRecord(profile).fails();
// mock.get('id') === profile.id
let mock = mockFindRecord('user').adapterOptions({friendly: true});
// used when urlForFindRecord (defined in adapter) uses them
urlForFindRecord(id, modelName, snapshot) {
if (snapshot && snapshot.adapterOptions) {
let { adapterOptions } = snapshot; // => {friendly: true}
// ... blah blah blah
}
// ... blah blah
}
mockFindAll
store.findAll(modelType)
mockFindAll
( fixture or model name, optional number, optional traits, optional attributes object)returns()
for controlling the response payload
adapterOptions()
for setting adapterOptions ( get passed to urlForFindAll )
mockFindAll
: users-view-test.jsUsage:
import { buildList, makeList, mockFindAll } from 'ember-data-factory-guy';
let mock = mockFindAll('user');
returns({json})
to return json object // that has 2 different users:
let users = buildList('user', 'whacky', 'silly');
let mock = mockFindAll('user').returns({ json: users });
let user1 = users.get(0);
let user2 = users.get(1);
// user1.style => 'whacky'
// user2.style => 'silly'
// or to acccomplish the same thing with less code
let mock = mockFindAll('user', 'whacky', 'silly');
let user1 = mock.get(0);
let user2 = mock.get(1);
// user1.style => 'whacky'
// user2.style => 'silly'
returns({models})
to return model instances let users = makeList('user', 'whacky', 'silly');
let mock = mockFindAll('user').returns({ models: users });
let user1 = users[0];
// you can now also user1.get('any-computed-property')
// since you have a real model instance
let users2 = buildList('user', 3);
mock.returns({ json: user2 });
fails()
method mockFindAll('user').fails();
mockReload
Usage:
let profile = make('profile');
mockReload(profile);
// will stub a call to reload that profile
profile.reload()
returns({attrs})
to return new attributes let profile = make('profile', { description: "whatever" });
mockReload(profile).returns({ attrs: { description: "moo" } });
profile.reload(); // description is now "moo"
returns({json})
to return all new attributes let profile = make('profile', { description: "tomatoes" });
// all new values EXCEPT the profile id ( you should keep that id the same )
let profileAllNew = build('profile', { id: profile.get('id'), description: "potatoes" }
mockReload(profile).returns({ json: profileAllNew });
profile.reload(); // description = "potatoes"
mockReload('profile', 1).fails();
mockQuery
store.query(modelType, params)
returns()
for controlling the response payload
withParams( object )
- withSomeParams( object )
mockQuery
: user-search-test.jsUsage:
import FactoryGuy, { make, build, buildList, mockQuery } from 'ember-data-factory-guy';
let store = FactoryGuy.store;
// This simulates a query that returns no results
mockQuery('user', {age: 10});
store.query('user', {age: 10}}).then((userInstances) => {
/// userInstances will be empty
})
// Create model instances
let users = makeList('user', 2, 'with_hats');
mockQuery('user', {name:'Bob', age: 10}).returns({models: users});
store.query('user', {name:'Bob', age: 10}}).then((models)=> {
// models are the same as the users array
});
// Create json with buildList
let users = buildList('user', 2, 'with_hats');
mockQuery('user', {name:'Bob', age: 10}).returns({json: users});
store.query('user', {name:'Bob', age: 10}}).then((models)=> {
// these models were created from the users json
});
// Create list of models
let users = buildList('user', 2, 'with_hats');
let user1 = users.get(0);
mockQuery('user', {name:'Bob', age: 10}).returns({ids: [user1.id]});
store.query('user', {name:'Bob', age: 10}}).then(function(models) {
// models will be one model and it will be user1
});
// Create list of models
let users = buildList('user', 2, 'with_hats');
let user1 = users.get(0);
mock = mockQuery('user').returns({ids: [user1.id]});
mock.withParams({name:'Bob', age: 10})
// When using 'withParams' modifier, params hash must match exactly
store.query('user', {name:'Bob', age: 10}}).then(function(models) {
// models will be one model and it will be user1
});
// The following call will not be caught by the mock
store.query('user', {name:'Bob', age: 10, hair: 'brown'}})
// 'withSomeParams' is designed to catch requests by partial match
// It has precedence over strict params matching once applied
mock.withSomeParams({name:'Bob'})
// Now both requests will be intercepted
store.query('user', {name:'Bob', age: 10}})
store.query('user', {name:'Bob', age: 10, hair: 'brown'}})
mockQueryRecord
store.queryRecord(modelType, params)
returns()
for controlling the response payload
Usage:
import FactoryGuy, { make, build, mockQueryRecord } from 'ember-data-factory-guy';
let store = FactoryGuy.store;
// This simulates a query that returns no results
mockQueryRecord('user', {age: 10});
store.queryRecord('user', {age: 10}}).then((userInstance) => {
/// userInstance will be empty
})
// Create model instances
let user = make('user');
mockQueryRecord('user', {name:'Bob', age: 10}).returns({model: user});
store.queryRecord('user', {name:'Bob', age: 10}}).then((model)=> {
// model is the same as the user you made
});
// Create json with buildList
let user = build('user');
mockQueryRecord('user', {name:'Bob', age: 10}).returns({json: user});
store.queryRecord('user', {name:'Bob', age: 10}}).then((model)=> {
// user model created from the user json
});
// Create list of models
let user = build('user', 'with_hats');
mockQueryRecord('user', {name:'Bob', age: 10}).returns({id: user.get('id')});
store.queryRecord('user', {name:'Bob', age: 10}}).then(function(model) {
// model will be one model and it will be user1
});
mockCreate
belongsTo
association, you don't have to include that in
the returns hash either ( same idea )true
if there is a match, false
otherwise.run
from @ember/runloop
and wrap tests using mockCreate
with: run(function() { 'your test' })
Realistically, you will have code in a view action or controller action that will create the record, and setup any associations.
// most actions that create a record look something like this:
action: {
addProject: function (user) {
let name = this.$('button.project-name').val();
this.store.createRecord('project', {name: name, user: user}).save();
}
}
In this case, you are are creating a 'project' record with a specific name, and belonging
to a particular user. To mock this createRecord
call here are a few ways to do this using
chainable methods.
Usage:
import { makeNew, mockCreate } from 'ember-data-factory-guy';
// Simplest case
// Don't care about a match just handle createRecord for any project
mockCreate('project');
// use a model you created already from store.createRecord or makeNew
// need to use this style if you need the model in the urlForCreateRecord snapshot
let project = makeNew('project');
mockCreate(project);
// Matching some attributes
mockCreate('project').match({name: "Moo"});
// Match all attributes
mockCreate('project').match({name: "Moo", user: user});
// Match using a function that checks that the request's top level attribute "name" equals 'Moo'
mockCreate('project').match(requestData => requestData.name === 'Moo');
// Exactly matching attributes, and returning extra attributes
mockCreate('project')
.match({name: "Moo", user: user})
.returns({created_at: new Date()});
// Returning belongsTo relationship. Assume outfit belongsTo 'person'
let person = build('super-hero'); // it's polymorphic
mockCreate('outfit').returns({attrs: { person }});
// Returning hasMany relationship. Assume super-hero hasMany 'outfits'
let outfits = buildList('outfit', 2);
mockCreate('super-hero').returns({attrs: { outfits }});
// Mocking failure case is easy with chainable methods, just use #fails
mockCreate('project').match({name: "Moo"}).fails();
// Can optionally add a status code and/or errors to the response
mockCreate('project').fails({status: 422, response: {errors: {name: ['Moo bad, Bahh better']}}});
store.createRecord('project', {name: "Moo"}).save(); //=> fails
mockUpdate
mockUpdate(model)
mockUpdate(modelType, id)
match
: takes a hash with attributes or a matching function
true
if there is a match, false
otherwise.run
from @ember/runloop
and wrap tests using mockUpdate
with: run(function() { 'your test' })
Usage:
import { make, mockUpdate } from 'ember-data-factory-guy';
let profile = make('profile');
// Pass in the model that will be updated ( if you have it available )
mockUpdate(profile);
// If the model is not available, pass in the modelType and the id of
// the model that will be updated
mockUpdate('profile', 1);
profile.set('description', 'good value');
profile.save() //=> will succeed
// Returning belongsTo relationship. Assume outfit belongsTo 'person'
let outfit = make('outfit');
let person = build('super-hero'); // it's polymorphic
outfit.set('name','outrageous');
mockUpdate(outfit).returns({attrs: { person }});
outfit.save(); //=> saves and returns superhero
// Returning hasMany relationship. Assume super-hero hasMany 'outfits'
let superHero = make('super-hero');
let outfits = buildList('outfit', 2, {name:'bell bottoms'});
superHero.set('style','laid back');
mockUpdate(superHero).returns({attrs: { outfits }});
superHero.save(); // => saves and returns outfits
// using match() method to specify attribute values
let profile = make('profile');
profile.set('name', "woo");
let mock = mockUpdate(profile).match({name: "moo"});
profile.save(); // will not be mocked since the mock you set says the name must be "woo"
// using match() method to specify a matching function
let profile = make('profile');
profile.set('name', "woo");
let mock = mockUpdate(profile).match((requestBody) => {
// this example uses a JSONAPI Adapter
return requestBody.data.attributes.name === "moo"
});
profile.save(); // will not be mocked since the mock you set requires the request's top level attribute "name" to equal "moo"
// either set the name to "moo" which will now be mocked correctly
profile.set('name', "moo");
profile.save(); // succeeds
// or
// keep the profile name as "woo"
// but change the mock to match the name "woo"
mock.match({name: "woo"});
profile.save(); // succeeds
let profile = make('profile');
// set the succeed flag to 'false'
mockUpdate('profile', profile.id).fails({status: 422, response: 'Invalid data'});
// or
mockUpdate(profile).fails({status: 422, response: 'Invalid data'});
profile.set('description', 'bad value');
profile.save() //=> will fail
mocking a failed update and retry with success
let profile = make('profile');
let mockUpdate = mockUpdate(profile);
mockUpdate.fails({status: 422, response: 'Invalid data'});
profile.set('description', 'bad value');
profile.save() //=> will fail
// After setting valid value
profile.set('description', 'good value');
// Now expecting success
mockUpdate.succeeds();
// Try that update again
profile.save() //=> will succeed!
mockDelete
run
from @ember/runloop
and wrap tests using mockDelete
with: run(function() { 'your test' })
Usage:
import { make, mockDelete } from 'ember-data-factory-guy';
let profile = make('profile');
mockDelete(profile);
profile.destroyRecord() // => will succeed
import { make, mockDelete } from 'ember-data-factory-guy';
let profile = make('profile');
mockDelete('profile', profile.id);
profile.destroyRecord() // => will succeed
import { make, mockDelete } from 'ember-data-factory-guy';
let profile1 = make('profile');
let profile2 = make('profile');
mockDelete('profile');
profile1.destroyRecord() // => will succeed
profile2.destroyRecord() // => will succeed
mockDelete(profile).fails();
mock
Well, you have read about all the other mock*
methods, but what if you have
endpoints that do not use Ember Data? Well, mock
is for you.
GET
, POST
, etc.) Defaults to GET
200
Usage:
import { mock } from 'ember-data-factory-guy';
this.mock({ url: '/users' });
import { mock } from 'ember-data-factory-guy';
this.mock({
type: 'POST',
url: '/users/sign_in',
responseText: { token: "0123456789-ab" }
});
The addon uses Pretender to mock the requests. It exposes the functions getPretender
and setPretender
to respectively get the Pretender server for the current test or set it. For instance, you can use pretender's passthrough feature to ignore data URLs:
import { getPretender } from 'ember-data-factory-guy';
// Passthrough 'data:' requests.
getPretender().get('data:*', getPretender().passthrough);
makeList
/buildList
and traits let json = buildList('widget', 'square', 'round', ['round','broken']);
let widgets = makeList('widget', 'square', 'round', ['round','broken']);
let [squareWidget, roundWidget, roundBrokenWidget] = widgets;
- you just built/made 3 different widgets from traits ('square', 'round', 'broken')
- the first will have the square trait
- the second will have the round trait
- the third will have both round and broken trait
import FactoryGuy from 'ember-data-factory-guy';
FactoryGuy.define('state', {
traits: {
NY: { name: "New York", id: "NY" },
NJ: { name: "New Jersey", id: "NJ" },
CT: { name: "Connecticut", id: "CT" }
}
});
// then in your tests you would do
let [ny, nj, ct] = makeList('state', 'ny', 'nj', 'ct');
import FactoryGuy from 'ember-data-factory-guy';
const states = [
{ name: "New York", id: "NY" },
{ name: "New Jersey", id: "NJ" },
{ name: "Connecticut", id: "CT" }
... blah .. blah .. blah
];
FactoryGuy.define('state', {
default: {
id: FactoryGuy.generate((i)=> states[i-1].id),
name: FactoryGuy.generate((i)=> states[i-1].name)
}
});
// then in your tests you would do
let states = makeList('state', 3); // or however many states you have
Example:
// file: tests/scenarios/admin.js
import Ember from 'ember';
import {Scenario} from 'ember-data-factory-guy';
export default class extends Scenario {
run() {
this.createGroups();
}
createGroups() {
this.permissionGroups = this.makeList('permission-group', 3);
}
groupNames() {
return this.permissionGroups.mapBy('name').sort();
}
}
// file: tests/acceptance/admin-view-test.js
import page from '../pages/admin';
import Scenario from '../scenarios/admin';
describe('Admin View', function() {
let scenario;
beforeEach(function() {
scenario = new Scenario();
scenario.run();
});
describe('group', function() {
beforeEach(function() {
page.visitGroups();
});
it('shows all groups', function() {
expect(page.groups.names).to.arrayEqual(scenario.groupNames());
});
});
});
assert.async()
(qunit) / done
(mocha) Sample testserialize()
methodmockUpdate
and mockCreate
means
that you can test a custom serialize()
method in a model serializer
// app/serializers/person.js
export default DS.RESTSerializer.extend({
// let's say you're modifying all names to be Japanese honorific style
serialize: function(snapshot, options) {
var json = this._super(snapshot, options);
let honorificName = [snapshot.record.get('name'), 'san'].join('-');
json.name = honorificName;
return json;
}
});
// somewhere in your tests
let person = make('person', {name: "Daniel"});
mockUpdate(person).match({name: "Daniel-san"});
person.save(); // will succeed
// and voila, you have just tested the serializer is converting the name properly
serialize()
method in a simpler way by doing this: let person = make('person', {name: "Daniel"});
let json = person.serialize();
assert.equal(json.name, 'Daniel-san');
FAQs
Factories for testing Ember applications using EmberData
The npm package ember-data-factory-guy receives a total of 4,355 weekly downloads. As such, ember-data-factory-guy popularity was classified as popular.
We found that ember-data-factory-guy demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.
Security News
Biden's executive order pushes for AI-driven cybersecurity, software supply chain transparency, and stronger protections for federal and open source systems.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.