Home

Awesome

Ember Data Factory Guy

Build Status Ember Observer Score npm version

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

NEW starting with v2.13.24

NEW and Improved starting with v2.13.22

Older but still fun things

Why is FactoryGuy so awesome

Questions / Get in Touch

Visit the EmberJS Community #e-factory-guy Slack channel

Contents

How it works

Installation

Upgrading

Setup

In the following examples, assume the models look like this:

  // standard models
  class User extends Model {
    @attr('string')     name
    @attr('string')     style
    @hasMany('project') projects
    @hasMany('hat', {polymorphic: true})  hats
  }

  class Project extends Model {
    @attr('string')     title
    @belongsTo('user')  user
  }

  // polymorphic models
  class Hat extends Model {
    @attr('string')     type
    @belongsTo('user')  user
  }

  class BigHat extends Hat {};
  class SmallHat extends Hat {};

Defining Factories

Standard models


  // 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'
    }
  });

// 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}`
  }
});

Polymorphic models


  // 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

Declaring sequences in sequences hash

  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'

Declaring an inline sequence on attribute

  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'

Inline Functions


  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

Traits


  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'

Using traits as functions
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

Associations

Setup belongsTo associations in Factory Definitions
  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"

Setup belongsTo associations manually

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
Setup hasMany associations in the Factory Definition

  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
Setup hasMany associations manually

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
Special tips for links

  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'}});

Extending Other Definitions

There is a sample Factory using inheritance here: big-group.js

Transient Attributes

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 class Dog extends Model{
    @attr('string') dogNumber
    @attr('string') sound
  }

  // 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`

Callbacks

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";

Using Factories

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

  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.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

Usage:


  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

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'}]);
Using add() method

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

Using get() method
  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"}

Using in Other Environments

Ember Data Model Fragments

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 TypeAssociation
fragmentFactoryGuy.belongsTo
fragmentArrayFactoryGuy.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 class Employee extends Model {
  @fragment('name') name
  @fragmentArray('phone-number') phoneNumbers
}

// Name fragment
export default class Name extends Fragment {
  @array('string')  titles
  @attr('string')   firstName
  @attr('string')    lastName
}

// Phone Number fragment
export default class PhoneNumber extends Fragment {
  @attr('string') number
  @attr('string') type
}

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:

Creating Factories in Addons

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.

Ember Django Adapter

  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

Custom API formats

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

This is helpful, when:

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
});

Testing models, controllers, components

Acceptance Tests

Using mock methods
setup
Using fails method
  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

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');
   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'
   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
   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

Usage:

   import { buildList, makeList, mockFindAll } from 'ember-data-factory-guy';
   let mock = mockFindAll('user');
   // 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'
    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 });
   mockFindAll('user').fails();
mockReload

Usage:

  let profile = make('profile');
  mockReload(profile);

  // will stub a call to reload that profile
  profile.reload()
  let profile = make('profile', { description: "whatever" });
  mockReload(profile).returns({ attrs: { description: "moo" } });
  profile.reload(); // description is now "moo"
  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

Usage:

  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

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

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

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

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.

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" }
  });

Pretender

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);

Tips and Tricks

Tip 1: Fun with 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

Tip 2: Building static / fixture like data into the factories.

  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

Tip 3: Using Scenario class in tests

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());
    });
  });
});

Tip 4: Testing mocks ( async testing ) in unit tests

Tip 5: Testing model's custom serialize() method


  // app/serializers/person.js
  export default class PersonSerializer extends RESTSerializer {

    // let's say you're modifying all names to be Japanese honorific style
    serialize(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
  let person = make('person', {name: "Daniel"});
  let json = person.serialize();
  assert.equal(json.name, 'Daniel-san');

ChangeLog