Giter Site home page Giter Site logo

factory-girl's Introduction

factory-girl

Build Status

factory-girl is a factory library for Node.js and the browser that is inspired by Factory_girl. It works asynchronously and supports associations and the use of functions for generating attributes.

Installation

Node.js:

npm install factory-girl

To use factory-girl in the browser or other JavaScript environments, there are builds for numerous module systems in the dist/ directory.

Usage

Refer to the tutorial for a gentle introduction of building a simple user factory.

Here's the crash course:

const factory = require('factory-girl').factory;
const User    = require('../models/user');

factory.define('user', User, {
  username: 'Bob',
  score: 50,
});

factory.build('user').then(user => {
  console.log(user); // => User {username: 'Bob', score: 50}
});

Defining Factories

Define factories using the factory.define() method.

For example:

// Using objects as initializer
factory.define('product', Product, {
  // use sequences to generate values sequentially
  id: factory.sequence('Product.id', (n) => `product_${n}`),
  // use functions to compute some complex value
  launchDate: () => new Date(),
  // return a promise to populate data asynchronously
  asyncData: () => fetch('some/resource'),
});
factory.define('user', User, {
  // seq is an alias for sequence
  email: factory.seq('User.email', (n) => `user${n}@ymail.com`),

  // use the chance(http://chancejs.com/) library to generate real-life like data
  about: factory.chance('sentence'),

  // use assoc to associate with other models
  profileImage: factory.assoc('profile_image', '_id'),

  // or assocMany to associate multiple models
  addresses: factory.assocMany('address', 2, '_id'),

  // use assocAttrs to embed models that are not persisted
  creditCardNumber: factory.assocAttrs('credit_card', 'number', {type: 'masterCard'}),

  // use assocAttrs or assocAttrsMany to embed plain json objects
  twitterDetails: factory.assocAttrs('twitter_details'),
});
// Using functions as initializer
factory.define('account', Account, buildOptions => {
  let attrs = {
    confirmed: false,
    confirmedAt: null
  };

  // use build options to modify the returned object
  if (buildOptions.confirmedUser) {
    attrs.confirmed = true;
    attrs.confirmedAt = new Date();
  }
  return attrs;
});

// buildOptions can be passed while requesting an object
factory.build('account', {}, {confirmed: true});

Options

Options can be provided when you define a factory:

factory.define('user', User, { foo: 'bar' }, options);

Alternatively you can set options for the factory that will get applied for all model-factories:

factory.withOptions(options);

Currently the supported options are:

afterBuild: function(model, attrs, buildOptions)

Provides a function that is called after the model is built. The function should return the instance or a Promise for the instance.

afterCreate: function(model, attrs, buildOptions)

Provides a function that is called after a new model instance is saved. The function should return the instance or throw an error. For asynchronous functions, it should return a promise that resolves with the instance or rejects with the error.

factory.define('user', User, {foo: 'bar'}, {
  afterBuild: (model, attrs, buildOptions) => {
    return doSomethingAsync(model).then(() => {
      doWhateverElse(model);
      return model;
    });
  },
  afterCreate: (model, attrs, buildOptions) => {
    modify(model);
    if ('something' === 'wrong') {
      throw new Error;
    }
    maybeLog('something');
    return model;
  }
});

Extending Factories

You can extend a factory using #extend:

factory.define('user', User, { username: 'Bob', expired: false });
factory.extend('user', 'expiredUser', { expired: true });
factory.build('expiredUser').then(user => {
  console.log(user); // => User { username: 'Bob', expired: true });
});

#extend(parent, name, initializer, options = {})

The #extend method takes the same options as #define except you can provide a different Model using options.model.

Using Factories

Factory#attrs

Generates and returns model attributes as an object hash instead of the model instance. This may be useful where you need a JSON representation of the model e.g. mocking an API response.

factory.attrs('post').then(postAttrs => {
  // postAttrs is a json representation of the Post model
});

factory.attrs('post', {title: 'Foo', content: 'Bar'}).then(postAttrs => {
  // builds post json object and overrides title and content
});

factory.attrs('post', {title: 'Foo'}, {hasComments: true}).then(postAttrs => {
  // builds post json object
  // overrides title
  // invokes the initializer function with buildOptions of {hasComments: true}
});

You can use Factory#attrsMany to generate a set of model attributes

factory.attrsMany('post', 5, [{title: 'foo1'}, {title: 'foo2'}]).then(postAttrsArray => {
  // postAttrsArray is an array of 5 post json objects
  debug(postAttrsArray);
});

Factory#build

Builds a new model instance that is not persisted.

factory.build('post').then(post => {
  // post is a Post instance that is not persisted
});

The buildMany version builds an array of model instances.

factory.buildMany('post', 5).then(postsArray => {
  // postsArray is an array of 5 Post instances
});

Similar to Factory#attrs, you can pass attributes to override or buildOptions.

Factory#create(name, attrs, buildOptions)

Builds a new model instance that is persisted.

factory.create('post').then(post => {
  // post is a saved Post instance
});

Factory#createMany(name, num, attrs, buildOptions = {})

The createMany version creates an array of model instances.

factory.createMany('post', 5).then(postsArray => {
  // postsArray is an array of 5 Post saved instances
});

Similar to Factory#attrs and Factory#build, you can pass attrs to override and buildOptions. If you pass an array of attrs then each element of the array will be used as the attrs for a each model created.

Factory#createMany(name, attrs, buildOptions = {})

If you can pass an array of attrs then you can omit num and the length of the array will be used.

Factory#cleanUp

Destroys all of the created models. This is done using the adapter's destroy method. It might be useful to clear all created models before each test or testSuite.

Adapters

Adapters provide support for different databases and ORMs. Adapters can be registered for specific models, or as the 'default adapter', which is used for any models for which an adapter has not been specified. See the adapter docs for usage, but typical usage is:

const FactoryGirl = require('factory-girl');
const factory = FactoryGirl.factory;
const adapter = new FactoryGirl.MongooseAdapter();

// use the mongoose adapter as the default adapter
factory.setAdapter(adapter);

// Or use it only for one model-factory
factory.setAdapter(adapter, 'factory-name');

ObjectAdapter

ObjectAdapter is a simple adapter that uses const model = new MyModel(), model.save() and model.destroy().

factory.setAdapter(new factory.ObjectAdapter());
class MyModel {
  save() {
    // save the model
  },
  destroy() {
    // destroy the model
  }
}
factory.define('model', MyModel);

Creating new Factories

You can create multiple factories which have different settings:

let anotherFactory = new factory.FactoryGirl();
anotherFactory.setAdapter(new MongooseAdapter()); // use the Mongoose adapter

History

This module started out as a fork of factory-lady, but the fork deviated quite a bit. This module uses an adapter to talk to your models so it can support different ORMs such as Bookshelf, Sequelize, JugglingDB, and Mongoose (and doesn't use throw for errors that might occur during save).

Version 4.0 is a complete rewrite with thanks to @chetanism.

License

Copyright (c) 2016 Chetan Verma.
Copyright (c) 2014 Simon Wade. Copyright (c) 2011 Peter Jihoon Kim.

This software is licensed under the MIT License.

factory-girl's People

Contributors

allie-wake-up avatar apolishch avatar aturkewi avatar bkstephens avatar brandonkboswell avatar bundacia avatar carlesnunez avatar celsomarques avatar chetanism avatar ejlangev avatar federicobond avatar jeffheifetz avatar jesseclark avatar jmnsf avatar joselion avatar kjjgibson avatar kmanzana avatar koshuang avatar melanievee avatar p-salido avatar petejkim avatar randyxli avatar rehanift avatar siboulet avatar simonexmachina avatar vitorbaptista avatar yads avatar yevhene avatar yourdeveloperfriend avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

factory-girl's Issues

Intercept cleanUp logic from adapter

In SQL based system, there is such thing as foreign key constraints which doesn't allow to remove records if they are referenced in other tables. This may be fixed if factory_girl remove all created records in reversed order (the most top record will be removed first).

factory.<method> with enabled adapter doesn't work when destructing <method>

import { factory, MongooseAdapter } from 'factory-girl';
import { Company } from 'model';

const adapter = new MongooseAdapter();
factory.setAdapter(adapter);

factory.define('company', Company, {
  name: factory.sequence('company.name', (n) => `Test Company ${n}`)
});

const { create } = factory;

async function wontwork ()
  await create('company');
});

It should be due to this which become undefined on destructing.

TypeError: Cannot read property 'getAdapter' of undefined
      at _callee3$ (node_modules/src/FactoryGirl.js:56:21)
      at tryCatch (node_modules/regenerator-runtime/runtime.js:65:40)
      at Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:303:22)
      at Generator.prototype.(anonymous function) [as next] (node_modules/regenerator-runtime/runtime.js:117:21)
      at step (node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
      at node_modules/babel-runtime/helpers/asyncToGenerator.js:35:14
      at F (node_modules/core-js/library/modules/_export.js:35:28)
      at node_modules/babel-runtime/helpers/asyncToGenerator.js:14:12
      at create (node_modules/factory-girl/index.js:899:22)
      at _callee3$ (test/unit/model/feature-test.js:24:11)
      at tryCatch (node_modules/regenerator-runtime/runtime.js:65:40)
      at Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:303:22)
      at Generator.prototype.(anonymous function) [as next] (node_modules/regenerator-runtime/runtime.js:117:21)
      at step (node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
      at node_modules/babel-runtime/helpers/asyncToGenerator.js:35:14
      at F (node_modules/core-js/library/modules/_export.js:35:28)
      at Context.<anonymous> (node_modules/babel-runtime/helpers/asyncToGenerator.js:14:12)

Note, the stack trace from actual code, not from the example above.

It is fine to use without destructing. But it will be good enhancement for the package if we can safe some typing with destructing methods from the factory object.

Thanks.

4.0.0-beta.1 broken?

Is 4.0.0-beta.1 broken? If I npm install factory-girl I see no lib or index.js in the module directory and I get Cannot find module 'factory-girl' error when I try to import it.

New stable release

Hello

I like your work. Factory_girl is module that is much needed in nodeJS community.
Unfortunately, I didn't managed to connect new factory_girl version with sequelize adapter factory_girl.
When do you think new stable version will be out?

`factory.assoc` doesn't work on nested properties / embeded

Hi,

As the title says, factory.assoc doesn't work when placed inside a nested properties. For example:

// Model definitions
mongoose.model('Currency', new mongoose.Schema({
  _id: String, // e.g. 'cad'
  name: String, // e.g. 'Canadian Dollar'
}));
var Currency = mongoose.model('Currency');

mongoose.model('Asset', new mongoose.Schema({
  name: String,
  currency: {
    type: String,
    ref: 'Currency',
    required: true,
  },
}));
var Asset = mongoose.model('Asset');

mongoose.model('User', new mongoose.Schema({
  name: String,
  assets: [ Asset.schema ],
  preferences: {
    currency: {
      type: String,
      ref: 'Currency',
      default: 'cad',
      autopopulate: true,
    },
  }
}));
var User = mongoose.model('User');

// Factory definitions
factory.define('currency', Currency, {
  _id: factory.sequence(function(n) { return 'cad' + n; }),
  sign: '$'
});

factory.define('user', User, {
  name: 'Test',
  // embeded object
  assets: [{
    type: 'other_asset',
    currency: factory.assoc('currency', '_id'), // Fails
    currency_value: 1.01,
    group: 'test',
  }],
  // nested properties
  preferences: {
    currency: factory.assoc('currency', '_id'), // Fails
  },
});

// This would fails
factory.create('user', function(err, user) { console.log(err, user) });

I know we have afterCreate, however we could never get to that point because of required: true validation in the embeded Asset.

afterBuild wouldn't work either, because the factory.assoc('currency', '_id') part is never called and considered a string (instead of a callback) when saving. Even if there's no such required: true validation, the object still won't save because it just isn't the valid data type.

I think the problem comes from asyncForEach here (https://github.com/aexmachina/factory-girl/blob/master/index.js#L200) doesn't go any deeper past 1st-level attribute keys. Maybe we can wrap the function in another recursive function?

Dynamically assigning an assoc object issue?

Hey, nice work!

However, I am having an issue here. It might not be related to factory-girl though, but it would be great if you could point me into the right direction.

I have 2 mongoose models: user and message.

// models/user.js
const { mongoose, db } = getMongoose()
const { Schema } = mongoose

const userSchema = new Schema({
  email: {
    type: String,
  },
})

const User = db.model('User', userSchema)
module.exports = User
// models/message.js
const { mongoose, db } = getMongoose()
const { Schema } = mongoose
const ObjectId = Schema.Types.ObjectId

const messageSchema = new Schema({
  _user: {
    ref: 'User',
    required: true,
    type: ObjectId,
  },
  text: {
    minlength: 2,
    required: true,
    type: String,
  },
})

const Message = db.model('Message', messageSchema)
module.exports = Message

As you can see, a message record has a user reference. Cool.

Next, I have factories for both:

// factories/user.js
const { factory } = require('factory-girl')
const faker = require('faker')
const User = require('models/user')

factory.define('user', User, {
  email: factory.seq('User.email', n => `${faker.internet.email()}_${n}`),
})

module.exports = factory
// factories/message.js
const { factory } = require('factory-girl')
const faker = require('faker')

const Message = require('models/message')
require('test/factories/user')

factory.define('message', Message, {
  _user: factory.assoc('user', '_id'),
  text: () => faker.lorem.paragraph(),
})

module.exports = factory

Next, I have a working spec in ava.js for a message model:

// test/models/message.js

import test from 'ava'
import factory from 'test/factories/message'

test('default factory is valid', async (t) => {
  const message = await factory.build('message')

  await message.validate()
  t.pass()
})
// ...

It passes, everything is awesome. But the problem is that when I want to have a test where I dynamically assign an assoc object to a message like this:

// test/models/message.js
import factory from 'test/factories/message'
// ...

test('validation succeeds when message has a user', async (t) => {
  const user = await factory.create('user')

  await factory.build('message', { _user: user }) // failing here
  t.pass()
})

It fails with this error:

TypeError: callback is not a function
    at /Users/cbrwizard/apps/squardak/node_modules/mongoose/lib/statemachine.js:136:14
    at Array.map (native)
    at ctor.map (/Users/cbrwizard/apps/squardak/node_modules/mongoose/lib/statemachine.js:135:29)
    at /Users/cbrwizard/apps/squardak/node_modules/src/utils/asyncPopulate.js:21:33
    at Array.map (native)
    at asyncPopulate (/Users/cbrwizard/apps/squardak/node_modules/src/utils/asyncPopulate.js:10:40)
    at /Users/cbrwizard/apps/squardak/node_modules/src/utils/asyncPopulate.js:19:17
    at Array.map (native)
    at asyncPopulate (/Users/cbrwizard/apps/squardak/node_modules/src/utils/asyncPopulate.js:10:40)
    at /Users/cbrwizard/apps/squardak/node_modules/src/utils/asyncPopulate.js:19:17
    at Array.map (native)
    at asyncPopulate (/Users/cbrwizard/apps/squardak/node_modules/src/utils/asyncPopulate.js:10:40)
    at /Users/cbrwizard/apps/squardak/node_modules/src/utils/asyncPopulate.js:19:17
    at Array.map (native)
    at asyncPopulate (/Users/cbrwizard/apps/squardak/node_modules/src/utils/asyncPopulate.js:10:40)
    at Factory._callee$ (/Users/cbrwizard/apps/squardak/node_modules/src/Factory.js:44:11)
    at tryCatch (/Users/cbrwizard/apps/squardak/node_modules/regenerator-runtime/runtime.js:64:40)
    at Generator.invoke [as _invoke] (/Users/cbrwizard/apps/squardak/node_modules/regenerator-runtime/runtime.js:299:22)
    at Generator.prototype.(anonymous function) [as next] (/Users/cbrwizard/apps/squardak/node_modules/regenerator-runtime/runtime.js:116:21)
    at step (/Users/cbrwizard/apps/squardak/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
    at /Users/cbrwizard/apps/squardak/node_modules/babel-runtime/helpers/asyncToGenerator.js:28:13
    at process._tickCallback (internal/process/next_tick.js:109:7)

Did I do something wrong here or is it a bug? Thank you!

Bookshelf Example?

Hi,

First, thanks for opening up the issues section. This probably isn't an issue at all, but something on my end. When using factory.build(), nothing seems to be saved to my database. I'm using Bookshelf; here's my code:

var factory = require('factory-girl'),
  BookshelfAdapter = require('factory-girl-bookshelf'),
  User = require('./models/user'),
  faker = require('faker');

var modelFactory = new factory.Factory();
modelFactory.setAdapter(BookshelfAdapter);

// define a factory using define()
factory.define('user', User, {
  firstName: faker.name.firstName(),
  lastName: faker.name.lastName(),
  email: faker.internet.email(),
  password: 'q1w2e3r4',
  active: false
});

factory.build('user', function(error, user) {
  console.log(user);
});

The object that's returned looks like this:

{ attributes: {},
  _previousAttributes: {},
  changed: {},
  relations: {},
  cid: 'c4',
  _events: { saving: [Function] },
  firstName: 'Tyree',
  lastName: 'Sporer',
  email: '[email protected]',
  password: 'q1w2e3r4',
  active: true }

All that said, my question is whether I'm using this properly with Bookshelf? I'm not sure if factory.build() calls save() or forge() in Bookshelf or if I need to do that on my own.

Thanks for your hard work on this module.

Best,
James

Variable `factory` in adapters section of readme is misleading

Trying to setup factory-girl with Mongoose.

From the top of the doc, I had const factory = require('factory-girl').factory;
When I tried to do:

const adapter = new factory.MongooseAdapter();

// use the mongoose adapter as the default adapter
factory.setAdapter(adapter);

// Or use it only for one model-factory
factory.setAdapter(adapter, 'factory-name');

I got the following error:

const adapter = new factory.MongooseAdapter();
                ^
TypeError: factory.MongooseAdapter is not a constructor

I believe in this section we should be doing something like:

const fg = require('factory-girl`)
const adapter = new fg.MongooseAdapter();

// use the mongoose adapter as the default adapter
factory.setAdapter(adapter);

// Or use it only for one model-factory
factory.setAdapter(adapter, 'factory-name');

TypeError: Model is not a constructor (mongoose

factory-girl version 4.2.2

I think this is related to #63. I am using Mongoose but otherwise used the same set up suggested there.

var fg = require('factory-girl');

var adapter = new fg.MongooseAdapter();
factory = fg.factory;
factory.setAdapter(adapter);

When running npm test I get:

TypeError: Model is not a constructor
    at MongooseAdapter.build (/Users/stoebelj/freecodecamp/voting/node_modules/factory-girl/index.js:703:14)
    at Factory._callee2$ (/Users/stoebelj/freecodecamp/voting/node_modules/factory-girl/index.js:155:33)
    at tryCatch (/Users/stoebelj/freecodecamp/voting/node_modules/regenerator-runtime/runtime.js:63:40)
    at Generator.invoke [as _invoke] (/Users/stoebelj/freecodecamp/voting/node_modules/regenerator-runtime/runtime.js:337:22)
    at Generator.prototype.(anonymous function) [as next] (/Users/stoebelj/freecodecamp/voting/node_modules/regenerator-runtime/runtime.js:96:21)
    at step (/Users/stoebelj/freecodecamp/voting/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
    at /Users/stoebelj/freecodecamp/voting/node_modules/babel-runtime/helpers/asyncToGenerator.js:28:13

Am I missing something?

running tests

Hi,

I was trying to run the tests, but it appears that maybe things aren't setup properly out of the box to transpile then test?

I keep getting:

(function (exports, require, module, __filename, __dirname) { import _extends from 'babel-runtime/helpers/extends';
                                                              ^^^^^^
SyntaxError: Unexpected token import

Thanks for the help!

Build with dependencies

I'd like to use factory-girl in a project which cannot use npm for dependency management.

As a new feature, could you include Grunt/Gulp build script to create a standalone .js file including dependencies e.g. into dist/factory-girl.js and dist/factory-girl.min.js

What type of models does this require?

We don't have any actual models in our code right now, just plain javascript objects that are returned as json from a REST API. Can we still use factory-girl? I don't see anything in the docs about what methods the models need to support.

duplicate entries when using buildMany

Hi,

I've defined a service factory and am attempting to use buildMany() to build a number of services. My service factory looks like so:

var faker = require('faker'),
    Service = require('../../models/service'),
    ServiceGroup = require('../../models/service_group');

module.exports = function (factory) {

  // default to bookshelf adapter
  require('factory-girl-bookshelf')();

  factory.define('service', Service, {
    active: false,
    archived: false,
    businessId: null,
    serviceGroupId: factory.assoc('serviceGroup', 'id'),
    primary: true,
    required: false,
    name: faker.commerce.productName(),
    description: faker.company.catchPhrase(),
    serviceType: ['walk', 'overnight', 'medication', 'feeding', 'custom'][faker.random.number(0, 4)],
    duration: faker.random.number(1, 48),
    basePrice: faker.commerce.price(),
    additionalPetCharge: faker.commerce.price(),
    recurringDiscountPrices: {
      twoTimesWeek: faker.commerce.price(),
      threeTimesWeek: faker.commerce.price(),
      fourTimesWeek: faker.commerce.price(),
      fiveTimesWeek: faker.commerce.price()
    },
    employeeRate: {
      amount: faker.commerce.price(),
      unit: ['percentage', 'dollar'][faker.random.number(0, 1)]
    },
    payrollExempt: false
  });

  return factory;
};

When I call buildMany(), it outputs completely identical objects rather than service objects with different values.

I should also note that I have a directory called factories/ with an index.js file that pulls in all of the files to make them available by importing that file. It looks like so:

const factory = require('factory-girl');

// set bookshelf as the default adapter
require('factory-girl-bookshelf')();

module.exports = {

  business: require('./business')(factory),
  businessPreference: require('./businessPreference')(factory),
  service: require('./service')(factory),
  serviceGroup: require('./serviceGroup')(factory),
  staffAccount: require('./staffAccount')(factory),
  surcharge: require('./surcharge')(factory),
  surchargeGroup: require('./surchargeGroup')(factory),
  user: require('./user')(factory)

};

I was passing in the factory instance to each to keep the instance consistent. However, there may be a better way to do this? Regardless, I thought this setup may have been the problem and instead put all of the factories into a single file, but I still have the same issue with duplicate objects. Is this expected behavior or is there something else I need to do/change?

Thanks!
James

Add possibility to extend factory

For example I have a Subscription model

factory.define('Subscription', Subscription, {
  id: factory.seq('id'),
  price: 500,
  name: 'Pay for 1 month'
})

And want to define factory for ExpiredSubscription which extends Subsription.

The same feature is implemented in Ruby's factory_girl using nested factories.

What's the best way to go about making a factory for a SubDocument?

In this particular case, I'm using mongoose.

Say I have a document Post with embedded Comments. I want to create specific comments through factories, but there isn't a Comment model from which I can base the factory.

Should I skip the adapter in this case and just use the schema in the factory declaration, or is there another way to do this?

Clean method undocumented

Hi,

I find an undocumented method clean.
For me this method is a life-savior, especially for test isolation.

Will you accept a PR to adding some docs on this point ?

Mongoose adapter is unable to handle dates

When I try create an object from a factory that has a date field, and error returns about casting.

Here is the simplest runnable case:

const mongoose = require('mongoose')
const factory = require('factory-girl').factory;
const factoryGirl = require('factory-girl')

// connect to mongo server with mongoose
mongoose.connect("localhost:27017/qbot_db_development")
mongoose.Promise = Promise

// setup factory with mongoose adapter
const adapter = new factoryGirl.MongooseAdapter();
factory.setAdapter(adapter);

// create some mongoose model
const shiftSchema = new mongoose.Schema({
  started_at: Date,
})
const Shift = mongoose.model('Shift', shiftSchema);

// define the factory witha  default time
factory.define('Shift', Shift, {
  started_at: new Date,
});

factory.create('Shift')
  .then((createdShift) => console.log(createdShift))
  .catch((err) => console.log(err))

I saved this in a file and when I run it, I get the following error:

// ♥ node scratch-pad.js 
{ ValidationError: Shift validation failed
    at MongooseError.ValidationError (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/error/validation.js:23:11)
    at model.Document.invalidate (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/document.js:1535:32)
    at model.Document.set (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/document.js:777:10)
    at model._handleIndex (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/document.js:623:14)
    at model.Document.set (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/document.js:583:24)
    at model.Document (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/document.js:71:12)
    at model.Model (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/model.js:51:12)
    at new model (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/model.js:3571:13)
    at MongooseAdapter.build (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/factory-girl/index.js:703:14)
    at Factory._callee2$ (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/factory-girl/index.js:155:33)
    at tryCatch (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/regenerator-runtime/runtime.js:64:40)
    at GeneratorFunctionPrototype.invoke [as _invoke] (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/regenerator-runtime/runtime.js:299:22)
    at GeneratorFunctionPrototype.prototype.(anonymous function) [as next] (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/regenerator-runtime/runtime.js:116:21)
    at step (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
    at /Users/avidor/Development/code/instructor/projects/qbot/node_modules/babel-runtime/helpers/asyncToGenerator.js:28:13
    at process._tickCallback (internal/process/next_tick.js:103:7)
  errors: 
   { started_at: 
      { CastError: Cast to Date failed for value "{}" at path "started_at"
          at MongooseError.CastError (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/error/cast.js:26:11)
          at model.Document.set (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/document.js:778:7)
          at model._handleIndex (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/document.js:623:14)
          at model.Document.set (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/document.js:583:24)
          at model.Document (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/document.js:71:12)
          at model.Model (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/model.js:51:12)
          at new model (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/mongoose/lib/model.js:3571:13)
          at MongooseAdapter.build (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/factory-girl/index.js:703:14)
          at Factory._callee2$ (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/factory-girl/index.js:155:33)
          at tryCatch (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/regenerator-runtime/runtime.js:64:40)
          at GeneratorFunctionPrototype.invoke [as _invoke] (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/regenerator-runtime/runtime.js:299:22)
          at GeneratorFunctionPrototype.prototype.(anonymous function) [as next] (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/regenerator-runtime/runtime.js:116:21)
          at step (/Users/avidor/Development/code/instructor/projects/qbot/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
          at /Users/avidor/Development/code/instructor/projects/qbot/node_modules/babel-runtime/helpers/asyncToGenerator.js:28:13
          at process._tickCallback (internal/process/next_tick.js:103:7)
          at Module.runMain (module.js:606:11)
        message: 'Cast to Date failed for value "{}" at path "started_at"',
        name: 'CastError',
        stringValue: '"{}"',
        kind: 'Date',
        value: {},
        path: 'started_at',
        reason: [Object] } },
  message: 'Shift validation failed',
  name: 'ValidationError' }

It looks like my date is getting converted into an empty object somehow.

Should Factory.build() create associated objects?

Is it expected behavior for factory-girl to create (save to db) associated objects when you call .build on a factory? If I'm calling .build I'm not expecting any docs to be saved to the database.

Reproduce code

var factory = require('factory-girl');
var db = require('myDbConnection');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.Types.ObjectId;

var userSchema = new Schema({
    post: {type: ObjectId, ref: 'user'}
});

var postSchema = new Schema({
    text: {type: String}
});

var UserModel = db('test').model('user', userSchema);
var PostModel = db('test').model('post', postSchema)

factory.define('user', UserModel, {
    post: factory.assoc('post', '_id')
});

factory.define('post', PostModel, {
    text: 'Hello.'
});

factory.build('user', {}, function(err, user){
    if(err) throw err;
    console.log('user: ', user);  
});

After the callback is executed user is a valid doc and has a valid _id but isn't saved to the db. Yet, Post was saved to the db. I would expect both to be non-saved models.

Circular Associations

I am trying to use factory-girl v4 (with the Sequelize adapter).

I have a User model that belongs to an Organization model, and I would like to have the Organization factory create an associated User and have the User factory create an associated organization.

When I try to do this using factory.assoc, I get an infinite loop as I assume the User and Organization factories are calling each other repeatedly.

Here's a sample of the code that I've tried:

factory.define('User', User, {
  // ...
  OrganizationId: factory.assoc('Organization', id),
})

factory.define('Organization', Organization, {
  // ...
  User: factory.assoc('User'),
})

Is this behavior supported by factory-girl, or can the associations only go one way? I could not find much documentation for the assoc function, so apologies in advance if there is something obvious I'm missing.

Thanks!

Template literal in factory.define() + factory.attrs() does not work properly

The factory.attrs() returns weird value when some property is defined in factory.define().

Here is my factory.define().

factory.define('mail', Mail, () => {
  const recipient = factory.chance('word');
  const secretCode = factory.chance('word');
  return {
    subject: factory.chance('sentence'),
    recipient,
    secretCode,
    to: [{
      address: () => `${recipient}@sh8.email`,
      name: factory.chance('name'),
    }, {
      address: factory.chance('email'),
      name: factory.chance('name'),
    }],
    from: [{
      address: factory.chance('email'),
      name: factory.chance('name'),
    }],
    cc: [{
      address: factory.chance('email', { domain: 'sh8.email' }),
      name: factory.chance('name'),
    }],
    bcc: [{
      address: factory.chance('email', { domain: 'sh8.email' }),
      name: factory.chance('name'),
    }],
    date: () => new Date(),
    messageId: factory.chance('hash'),
    text: factory.chance('paragraph'),
    html: () => `<p>${factory.chance('paragraph')}</p>`,
  };
});

And let's use the factory.attrs().

factory.attrs('mail').then((mail) => {
      console.log(mail);
})

Result:

{ to:
   [ { address: 'function () {\n      return generator.generate.apply(generator, args);\n    }@sh8.email',
       name: 'George Oliver' },
     { address: '[email protected]', name: 'Leona Hicks' } ],
  from: [ { address: '[email protected]', name: 'Rosa Brady' } ],
  cc: [ { address: '[email protected]', name: 'Lela Ramos' } ],
  bcc: [ { address: '[email protected]', name: 'Lucas Webster' } ],
  subject: 'Ha heni icociwuv vi kewetdus zalhupep fa za ehaiztes zareav wo of kodeceigo ceza jatamuho ped no.',
  recipient: 'dapozgi',
  secretCode: 'mekip',
  date: 2017-03-18T14:06:04.149Z,
  messageId: 'ea8964a8bc41eb587d2be29f3ddb433c4a15cbaa',
  text: 'Duk ri ce nemmucpu guklizzu pa cud mebedmuh nagvecuf sogu vugtoh jojpi paub. Sehecehu aceda wal wel pugmu zinpuno epi ke mo cis emzu sihneezi watatson uma kacemtel loko pon. Foka bulolid jebde oguje pal hiwvot duguciweb azicer roari efko dod ebalileta. Nefole pi lo pijjoova agakikpo ifsif aftani iwe huguhafu ba kicepa siza sowi. Janu ipi soenfe arueci iluiz boasge esvag haholhih oma bahmubza ledeg ediguse erukemem puji. Asbuvbi luz upcerude gulbooji vaic eszefsol fi lan kor ziw ri oh ug sat zuc pa woniz zijkop.',
  html: '<p>function () {\n      return generator.generate.apply(generator, args);\n    }</p>' }

The template literals were converted as some anonymous function using generator.

Testing Environment

  • MacOS Sierra
  • node v6.9.2
  • factory-girl ^4.2.1

Issues on introduction to project

It is great project, this is very cool that it has so many abilities,
though common use of factories usually is simple, like:

factory.define('post', Post, {
    title: 'Втащили по клюву!',
    rate: 2
});

And when you look at readme - it looks overcomplicated
I think, many people may stop at this point and then try to look for alternatives.

It would be more friendly to have first example simple - to show, that it is really proper tool for factories.
(Programmer would use ORM itself for populating database, if he wouldn't look for simplicity)

Like here - first example shows only essentials: https://mochajs.org/
And reader understands - that's easy tool for testing.

afterCreate and buildMany

Hi,

I discovered the afterCreate opiton while looking at the source code. It be great if the afterCreate option was documented in the README. This option is essential when using ex. passport-local-mongoose to set user password by calling setPassword() method before returning the created model.

Also, I noticed afterCreate is only called when using factory.create(), but not when using factory.createMany(). Is this expected behavior?

Thanks

Method to do "upsert"

Hi y'all! I have a model with such an unique index:

  schema.index({ userId: 1, labelId: 1 }, { unique: true })

So I find myself repeating the following code:

Model.findOne({ clientId, labelId }).then(instance => {
  if (instance) return instance
  return factory.create('model', { clientId, labelId })
})

Would there be a better way of doing this through factory-girl?

Thank you!

ValidationError in v4

I've upgraded to v4 and now I can't pass ObjectIds into create functions.

  factory.create('car', { owner: owner.id }) // this works
  factory.create('car', { owner }) // this does NOT
  factory.create('car', { owner: owner._id }) // this does NOT

Error

message: 'Car validation failed',
name: 'ValidationError',
errors:
 { capability:
    { CastError: Cast to ObjectID failed for value "{ id:
       { '0': 88,
         '1': 17,
         '2': 19,
         '3': 3,
         '4': 54,
         '5': 228,
         '6': 171,
         '7': 60,
         '8': 100,
         '9': 82,
         '10': 176,
         '11': 71 },
      _bsontype: 'ObjectID' }" at path "owner"
        at MongooseError.CastError (/home/test/node_modules/mongoose/lib/error/cast.js:26:11)

How to create multiple objects synchronously?

I'm looking for something like:

const user = factory.create('user');
const client = factory.create('client');
const books = factory.createMany('book', 10);

// Now that everything has been created, let's proceed with our mocha test

Instead, what I'm ending up doing in my mocha test is something like:

before((done) => {
  factory.create('user', (user) => {
    this.user = user;
    factory.create('client', (client) => {
      this.client = client;
      factory.createMany ... 
          // Call done() somewhere
    });
  });
});

Can you help me how multiple create calls are supposed to be used? I would also appreciate some examples in the README. Great job with the lib by the way ;)

.idea is in your npm published package

You may be globally ignoring .idea. Yet when npm publishing it will be included.

Eventually this will get easier:
npm/npm#5673

Publishing folders by accident can lead to exposing private information that you may otherwise not want to be made available.

including/omitting specific properties

Hi again,

Wanted to see what your thoughts were on having the option to either include or omit specific properties from the factory? For example, if I wanted to do a partial update of a person object rather than all the fields, I may only want a subset of the available properties. I was thinking something like:

factory.build('user', { omit: [ 'email', 'password'] }, function(err, user) {
    // all properties returned except email and password
    console.log(user.attributes);
});

Best,
James

v4.2.2 cleanUp() fail with children records

Currently the cleanUp invokes destroy with the order that was created. When you have models with relationship like Author has many Book. You create an author and then some books with the created author, cleanUp() will try to destroy the author because it was created first but it will fail with foreign key constraint error since books are still referencing it (I'm using postgres btw and no cascade delete).
It would be nice the cleanUp invokes destroy in reverse order. Thanks!

REDUX ORM Adapter created

Hello @aexmachina

I've been working with factory-girl with redux-orm and I'll would like to integrate the adapter with that awesome factory library.

I've made the tests and checked eslint style. We can discuss anything else if you like but this is working great for me on my actual project.

Here is the PR
#87

v4 cleanUp() problems

Hello.

I'm using factory-girl 4.0.0-beta.7 with sequelize and mocha. I've noticed a few problems:

Sync version of build?

Maybe this isn't the place for feature requests (let me know if there's a better place), and unfortunately I'm fairly new to Node and newer JS functions, so I don't have a PR for this, but:

It sure would be great to have a .build_sync function, or perhaps a sync flag for the build function. I'm having a heck of a time awaiting the promise in my mocha test suite (my environment doesn't support await). I know this was built at least partly to solve the problem of needing async factories (for performance, or whatever), but if adding a sync option isn't terribly difficult, this would certainly be the best sync factory library I have found so far.

Duplicating assigned attributes

Consider the following factory:

const User = require('models/user');

factory.define('user', User, {
  username: factory.sequence(n => `sherlock_${n}`),
  name: "Sherlock Holmes",
  password: "Password",
  passwordConfirmation: function() { return this.password; }
});

I'd expect the following to pass.

factory.build('user', { password: 'secret' })
  .then(user => expect(user.passwordConfirmation).to.equal('secret');

The syntax could differ but I think it's common use case to duplicate (or do something else) on an already assigned attribute.

promisify?

At this point, you probably hate me for asking 20 questions, but last one, I promise :)

I saw that there was a factory.promisify function in the code where you can pass a promisify library into it. I tried this with Bluebird, but no luck -- I get an undefined error. Does this need to be called after all of the factories have been defined in order to work properly?

Thank you!
James

Many to Many with Sequelize

Hello... factory-girl works perfect but now I have a case where I have many-to-many on sequelize and I want to know how possible could be to send extra options into .build function so the adapters could use that... for example on sequelize if we have belongsToMany we could send the option

return Model.build(props, {
  include: Job // Job is a sequelize model
}

what do you think? do you see it possible?

Ability to run natively in Node.js

I've made some changes to factory-girl so that it runs natively (without Babel pre-compilation) in Node. Only problem is the build script complains that there's no default export because it uses CJS modules. There's a Babel plugin that fixes this, but it doesn't seem to work with the Rollup build configuration that we're using, and I don't have a good understanding of how our build system works. I've pushed my changes to the cjs branch if anyone is interested to take a look.

How to create many-to-many associations with Bookshelf?

Hi,

I'm sorry if GitHub Issues isn't the right place to ask these questions. I'd be happy to re-ask them in StackOverflow if needed.

Considering the many-to-many association example in bookshelfjs.org:

var Book = bookshelf.Model.extend({
  tableName: 'books',
  authors: function() {
    return this.belongsToMany(Author);
  }
});

var Author = bookshelf.Model.extend({
  tableName: 'authors',
  books: function() {
    return this.belongsToMany(Book);
  }
});

This needs a books and authors table, and also an association table authors_books. I tried using authors: factory.assocMany('author', 'id', 2) in the Book factory, but it ended up trying to insert an authors column in the DB. Are these kind of associations supported? If not, is there any workaround?

model.build is not a function using sequelize adapter

Hi guys !, I'm currently doing a small api on express and I'm running into an error when trying using factory.create('director') or factory.build('director') on my tests.
Here is the stacktrace:

TypeError: Model.build is not a function
    at SequelizeAdapter.build (~/node_modules/factory-girl/index.js:1270:20)
    at Factory._callee2$ (~/node_modules/factory-girl/index.js:155:33)
    at tryCatch (~/node_modules/regenerator-runtime/runtime.js:65:40)
    at Generator.invoke [as _invoke] (~/node_modules/regenerator-runtime/runtime.js:303:22)
    at Generator.prototype.(anonymous function) [as next] (~/node_modules/regenerator-runtime/runtime.
js:117:21)
    at step (~/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
    at ~/node_modules/babel-runtime/helpers/asyncToGenerator.js:28:13
    at <anonymous>

I believe it is a problem on how I'm using the library but can't figure out why. I setup the factory guiding myself through #63 (comment) but added it into an index.js file in the directory so I could export the factory to all of the rest of the files without having to repeat the process per model definition:

const fs = require('fs');
const path = require('path');
const _ = require('lodash');

var factory = require('factory-girl');
const adapter = new factory.SequelizeAdapter();
factory = factory.factory;
factory.setAdapter(adapter);

let dir = path.join(__dirname, 'lib');

// For each file in lib directory
fs.readdirSync(dir).forEach((file) => {

  // Only process JS files
  if (file[0] !== '.' && _.endsWith(file, '.js')) {
    let basename = path.basename(file, '.js');
    let mod = require(path.join(dir, basename));

    // Call exported function with our defined factory
    mod(factory);
  }
});

module.exports = factory;

and my factory as follows:

// Common libs
const casual = require('/casuals');
const Director = require('/models/director.js');

module.exports = (factory) => {
  factory.define('director', Director, {
    name: () => casual.first_name,
    email: () => casual.email,
  });

};

any help is greatly appreciated, thanks !

Is there a way to pull multiple fields from the same association

I have a base object User

factory.define('user', User, (buildOptions) => {
    return {
        fn: factory.chance('first'),
        ln: factory.chance('last'),
        email: factory.sequence('User.email', (n) => `dummy-user-${n}@my-domain.com`)
    };
});

And I want to have a childObject SuperUser with the id, fn, ln and email fields. Is there any way to do this? I was playing with the following but it didn't seem to work

factory.define('superuser', SuperUser, (buildOptions) => {
    return Promise.resolve().then(() => {
        if (!buildOptions.user) {
            return factory.create('user');
        } else {
            return Promise.resolve(buildOptions.user);
        }
    }).then((user) => {
        return {
            uid: user._id,
            fn: user.fn,
            ln: user.ln,
            powers: {
              flight: factory.chance('bool'),
              strength: factory.chance('bool')
            }
        };
    });
});

new adapter + babel setup

Hi,

I'm working on an adapter for Objection.js and ran into the following error when attempting to run tests for it:

ObjectionAdapter #build builds the model:
     TypeError: Class constructor Model cannot be invoked without 'new'
      at new DummyObjectionModel (test/test-helper/DummyObjectionModel.js:32:145)

Looking at the Objection repo, they claim it has something to do with the Babel setup.

Do you have any thoughts on what would need to be changed to make things work with the current Factory Girl setup? I'm a complete newb with regards to Babel, so was hoping you might have some advice.

Thanks!

inheritance and traits?

Hello thank you and great job! I remember with rails we could use inheritance and traits to specify objects with more specific attributes. Is there an equivalence here or do you suggest an alternative implementation? Thank You

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.