Code Monkey home page Code Monkey logo

meteor-factory's Introduction

Meteor Factory

A package for creating test data or for generating fixtures.

Installation

meteor add dburles:factory

Table of Contents

Examples

Defining factories

Authors = new Meteor.Collection("authors");
Books = new Meteor.Collection("books");

Factory.define("author", Authors, {
  name: "John Smith",
}).after((author) => {
  // Do something smart
});

Factory.define("book", Books, {
  authorId: Factory.get("author"),
  name: "A book",
  year() {
    return _.random(1900, 2014);
  },
});

// We can also extend from an existing factory
Factory.define(
  "anotherBook",
  Books,
  Factory.extend("book", {
    // ...
  })
);

Creating documents

// Ex. 1: Inserts a new book into the books collection
const book = Factory.create("book");

// Ex. 2: New fields can be added or overwritten
const book = Factory.create("book", { name: "A better book" });

API

Note: When calling Factory.create('book') both the Book and an Author are created. The newly created Author _id will then be automatically assigned to that field. In the case of calling Factory.build('book') as no insert operations are run, the _id will be faked.

define

Factory.define('name', Collection, doc).after(doc => { ... })

  • name
    • A name for this factory
  • Collection
    • A meteor collection
  • doc
    • Document object
  • .after hook (Optional)
    • Returns the newly inserted document after calling Factory.create

get

Factory.get('name')

Returns the instance of name. Typical usage is to specify a relationship between collections as seen in the Book example above.

build

Factory.build('name', doc)

Builds the data structure for this factory

  • name
    • The name defined for this factory
  • doc (Optional)
    • Document object

buildAsync

Asynchronous version of build. Returns a Promise.

tree

Factory.tree('name', doc)

Builds an object tree without _id fields. Useful for generating data for templates.

  • name
    • The name define for this factory
  • doc (Optional)
    • Document object

Example:

Factory.define("author", Authors, {
  name: "John Smith",
});

Factory.define("book", Books, {
  name: "A book",
  author: Factory.get("author"),
});

const book = Factory.tree("book");

book then equals:

{
  name: 'A book',
  author: {
    name: 'John Smith'
  }
}

treeAsync

Asynchronous version of tree. Returns a Promise.

create

Factory.create('name', doc)

Creates (inserts) this factory into mongodb

  • name

    • The name defined for this factory
  • doc (Optional)

    • Document object

createAsync

Asynchronous version of create. Returns a Promise.

extend

Factory.extend('name', doc)

Extend from an existing factory

  • name
    • The name defined for this factory
  • doc (Optional)
    • Document object

Contributing

Testing

Please submit new pull requests with tests if applicable. To run the test suite, run the following command:

meteor test-packages ./

Then open a browser at localhost:3000 (by default).

Other

Fake makes a great companion package. See https://atmospherejs.com/anti/fake

License

MIT. (c) Percolate Studio

factory was developed as part of the Verso project.

meteor-factory's People

Contributors

dburles avatar floriferous avatar minhna avatar nachocodoner avatar timbotnik avatar tmeasday avatar wagerfield 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

meteor-factory's Issues

Nesting relationships inside arrays of objects fails with Maximum call stack size exceeded

The following factory creates an infinite loop:

  Factory.define("book", Books, { links: [{ _id: Factory.get("author") }] });

The following workaround seems to work:

  Factory.define("book", Books, { links: () => [{ _id: Factory.create("author")._id }] });

The code that loops over arrays does not use the same recursive logic as the rest of the values, but it doesn't look trivial to update.

Add auto-incremental fields

Helpful for generating sequence of e.g. ids or names.

For example:

Factory.define('place', Places, {
  name: Factory.autoIncrement('Place')
})

Factory.build('place') // returns { name: 'Place 0' }
Factory.build('place') // returns { name: 'Place 1' }
Factory.build('place') // returns { name: 'Place 2' }

No way to automatically create parent when child is created

I have a relationship like this:

Authors = new Meteor.Collection('authors');
Books = new Meteor.Collection('books');
Factory.define('author', Authors, {name: 'John Smith'});
Factory.define('book', Books, { author: Factory.get('author') });

and would like a book to be created whenever I create an author. I tried adding a link from the author back to the book, but that causes a stack overflow exception (I assume the author creates a book which creates a new author etc.).

Can you suggest any other ways of handling this?

Inconsistent errors server-side, factory not created every time

I'm having lots of trouble making my tests run consistently every time, with errors coming from this package. It is very well possible that it is my fault, but I don't understand why.

This is one of the errors I see on the first run of my tests (though not every time), and not on a subsequent rerun of my tests, only on the server, never on the client:

Error: Cannot read property '_id' of undefined
    at makeRelation (packages/dburles:factory/factory.js:45:14)
    at packages/dburles:factory/factory.js:71:20
    at Function._.each._.forEach (packages/underscore.js:147:22)
    at walk (packages/dburles:factory/factory.js:67:7)
    at Function.Factory._build (packages/dburles:factory/factory.js:97:3)
    at Function.Factory.create (packages/dburles:factory/factory.js:121:23)
    at Hook.beforeEach2 (imports/api/loanrequests/test/loanRequests.test.js:67:27)
    at run (packages/practicalmeteor:mocha-core/server.js:34:29)

On circle CI, this test keeps failing but with a timeout error:
timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.

These are the 3 factories that are involved in this error:

// Top level
Factory.define('loanRequest', LoanRequests, {
  userId: Factory.get('user'),
  createdAt: () => new Date(),
  general: () => ({
    fortuneUsed: 250000,
    insuranceFortuneUsed: 0,
    partnersToAvoid: ['joe', 'john'],
  }),
  borrowers: [Factory.get('borrower')],
  property: () => ({ value: 1000000 }),
  files: () => ({}),
  logic: () => ({ auction: {}, lender: {}, verification: {}, step: 1 }),
  name: () => 'request name',
  emails: () => [],
});

// Linked
Factory.define('user', Meteor.users, {
  roles: () => 'user',
  emails: () => [{ address: chance.email(), verified: false }],
});

Factory.define('borrower', Borrowers, {
  userId: Factory.get('user'),
  createdAt: () => new Date(),
  expenses: () => [{ description: 'test', value: 1 }],
  files: () => ({}),
  logic: () => ({}),
});

And this is the structure of my test file:

describe('loanRequests', function () {
  beforeEach(function beforeEach1() {
    // xolvio:cleaner
    resetDatabase();
  });

  describe('modifiers', () => {
    let request;
    beforeEach(function beforeEach2() {
      request = Factory.create('loanRequest');
    });

    it('should work', () => {
      // This is where it fails
      const id = request._id;

      // ..test..
    })
  });
});

Cannot define _id

When there is a nested _id property for a document, Factory cannot set the value of it and throw an error.

Reproduction:

Factory.define('myColection', MyColection, {
  name: 'something'
  owner: {
    _id: 'testOwnerId',
    name: 'testOwnerName',
  },
});

When creating a doc, it throws:

Mod on _id not allowed
MinimongoError: Mod on _id not allowed

But when I insert the same doc throw browser console or mongo shell, it works.

after hook for .build

How about making after hook run for Factory.build()? Currently it only runs for Factory.create().

A useful case would be when simulating a document not yet inserted to the db.

// post object not persisted yet
Factory.define('newPost', Posts, {
  body: 'testBody',
}).after(function (post) {
  delete post._id;
});

Reference other fields in Factory.define

Hi, certain fields in my collection depend on other field values. How can a obtain the value of a field in definition?
Example:

Factory.define 'product', App.collections.products,
sex: -> getRandomElementFromList ['Male', 'Female']
category: -> # This value depends on the chosen sex value.

Thanks.

Make the package DebugOnly

I can't see why anyone would want factories in production, but I may be wrong.
Is there a reason to not add debugOnly: true?

Add ability to pass options to the factory definition

Suppose I have a single collection where each document has a child array

{
   name: "Charles Dickens",
   books: [
      {  name: "Great Expectations"  },
      {  name: "A Tale of Two Cities" }
   ]
}

Now I want to be able to create a document with a specific number of books. I'd like to be able to have access to an options object within my factory functions. Like this:

Factory.define('authors', Authors, {
   name: faker.fake("{{name.lastName}}, {{name.firstName}}"),
   books: function (options) {
       _(options.bookCount).times( function (n) {
            return ({ name: faker.lorem.words() })
    }
}  

Then I could create documents like this:

var author = Factory.create('authors', {}, { bookCount: 25})

To create a document with 25 books.

Simpleschema autoValue with this.userId

Hi,

I'm implementing some unit tests for my Meteor methods and I'm running into the issue that I can't create a simple object with a factory. The reason for this is simpleschema trying to add this.userId as the author in my colletion.

I have the following set in my schema:

author: {
   type: String,
   autoValue: function () {
      if (this.isInsert) {
         return this.userId;
      }
   }
}

When running my tests I get the error that author has to be set, and even if I add it to the factory, the simpleschema will set it to undefined because this.userId is not set.

I initially thought of creating a user and login in as the user, but I haven't been able to get this working on the server side. Alternatively, I read something about replacing this.userId with a default value, but I'm not sure how to go and implement that. Would you have any suggestions on how to go ahead and solve this issue?

Thank you in advance!

Create user account within factory

I'm having trouble creating a user account on the fly. Something like this:

Factory.define('team', Teams, {
  name: () => faker.lorem.words(),
  userId: () => {
    const email = faker.internet.email();
    const password = faker.lorem.words(1);
    Accounts.createUser({ email, password });

    if (Meteor.userId()) return Meteor.userId();
    return Accounts.findUserByEmail(email)._id;
  },
});

Since this factory can be both run on client and server, there are differences in how Meteor.userId() works.

On client tests, I get this error: Accounts.findUserByEmail is not a function
On server tests, I get: Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions. (but using this.userId results in a different error)

Any tips? Great package btw!

how to setup dependent properties?

not sure if there's a way to do this or not, looking at the code so far I think not...

but I want to be able to say something like:

Factory.define('event', Actions, {
    end () {
      if (!this.start || !this.duration) {
        throw new Error('must provide start and duration if end not specified')
      }
      return new Date(+this.start + this.duration)
    },
})

That would allow me to optionally provide start and duration. I notice that this object contains the statically defined properties, but not what the user provides as arguments to Factory.create

Any tips on how to implement that?

Hooks don't get called

This is my helper

Posts.before.insert(function (userId, doc) {
    doc.createdAt = moment().toDate();
});

and schema

PostsSchema = new SimpleSchema({
    title: {
        type: String
    },
    createdAt: { // this is required
        type: Date
    }
});

This works fine when i am inserting doc on client side. But when using the factor the hooks don't get called before insert.

Factory.define('post', Posts, {
    title: function () {
        return Fake.word();
    }
});

Factory.get returns Circular object

I am currently setting up a testing environment for a new project.

I am in the process of debugging some errors, and wanted to know what object get returned since it did not return what I expected. Upon printing in the console it printed [Circular].

I slimmed down everything and ended up with this basic code that you would be able to use to reproduce the error.

import { Mongo } from 'meteor/mongo'
import { Factory } from 'meteor/dburles:factory';
import { assert } from 'chai';

describe('ItemTester', function() {
	let Items = new Mongo.Collection('items');

	beforeEach(function() {
		Factory.define('item', Items,
			{a: "b"}
		);
	});

	afterEach(function() {
		
	});

	it('Should load nodes by ID', function() {
		console.log(Factory.get('item'));
		assert.equal(1, 1);
	});
});

Factory & Simple-Schema – ID is required

Hi,

I'm using meteor factory to create documents for testing and whenever I try to .create('something') whose collection has a schema attached to it, I get an error "ID is required" on insert from the Factory.

I see that SimpleSchema is getting called on insert so I called .create('something', {_id: Random.id()}) and still get the same error.

Is there a way to use this package with collections that have schemas attached to it?

Simple way for getting related objects in a single create call?

When you define a collection with relationships like this:

Factory.define('book', Books, {
  authorId: Factory.get('author'),
  name: 'A book',
  year() { return _.random(1900, 2014); }
});

And then when calling:

const book = Factory.create('book');

Is there a simpler way to get the author object that I'm missing? Or should I do this:

const author = Authors.find({ _id: book.authorId });

Meteor 1.3 and testing packages

I seem to be missing something. When following the README but using Factory in a package I get errors like this:

TypeError: Object function (collection, attributes) {                                                         
W20160407-10:40:12.710(-7)? (STDERR)   this.collection = collection;                                                                     
W20160407-10:40:12.710(-7)? (STDERR)   this.attributes = attributes;                                                                      
W20160407-10:40:12.710(-7)? (STDERR)   this.afterHooks = [];                                                                              
W20160407-10:40:12.710(-7)? (STDERR) } has no method 'define'

Thank you!

Feature request: Hooks before insertion of document

Sometimes some fields need to be randomised based on other fields within the document. For eg. DateResponded property should contain a timestamp that is later than DateRequested property for each document.

get _id from Factory.get('name')

Hi,
Is there a possibility to get id of created instance. If I call Factory.get('name') I dont see any _id field
Thanks

Best Regards

Problem with define custom "_id"?

I have problem with custom _id

Factory.define('location', Sample.Collection.Location, {
    _id: function () {
        return '001';
    },
    name: faker.address.city()
});

Cannot create a has many relation

We have a case where a parent has a list of child ids. There's no way to specify that the child ID should be in an array – trying to do [Factory.get("child")] throws an error (RangeError: Maximum call stack size exceeded).

Test case:

Authors = new Meteor.Collection('authors');
Books = new Meteor.Collection('books');
Factory.define('author', Authors, {name: 'John Smith'});
Factory.define('book', Books, { authorIds: [Factory.get('author')] });
Factory.create("book")

throws the error instead of creating the book.

Factory.create returns undefined inconsistently

This is related to issue #32.

I have tests wrapped like this one :

describe('Testing DB', () => {
  it('should create model', dbTest(() => {
    const user = Factory.create('user');
    expect( user ).to.not.be.undefined;
  }));
});

Where the dbTest function is defined as such :

export function dbTest(handler) {
  if (Meteor.isServer) {
    return function run() {
      return new Promise((resolve, reject) => {
        const runner = Meteor.bindEnvironment(() => {
          Fiber(function () {
            try {
              Promise.resolve(handler()).then(resolve, reject);
            } catch (error) {
              reject(error);
            }
          }).run();
        });
        runner();
      });
    }
  } else {
    return handler;
  }
};

(BTW: I have tried swapping the Fiber and Meteor.bindEnvironment, but nothing is different, so...)

I have tried several implementations, and this is the one that gives the least amount of failure. But some tests still fail because the created model is undefined. The errors are inconsistent, so I cannot pinpoint exactly where or what may be causing this. But it is always associated with models created from Factory.

Some of these tests directly fetch models from the collections, and I have yet to see any failure there; but all the random failures are from Factory.create.

Simplify creating a Meteor user

Example factory for Meteor users:

Factory.define('user', Meteor.users, {
  password: () => faker.internet.password(),
  emails: () => [{ address: () => faker.internet.email(), verified: true }],
})

This returns a function when accessing emails[0].address. Related to test Factory - Build - Array with an object containing a function.

My workaround:

const buildEmail = () => [{ address: faker.internet.email(), verified: true }];

Factory.define('user', Meteor.users, {
  password: () => faker.internet.password(),
  emails: () => buildEmail(),
})

Factory: There is no factory name error

Hi,

When I'm trying to run the meteor app with all the factories defined and associated with Factory.get('name'), the tests seems to work fine on meteor test --driver-package [Driver] command but not when I simply run meteor command to run the app.

I checked for typos with the defined Factory names and everything seems to be correctly defined.

Thanks!
-Infi

Faster insertion

Hello,
I am starting to use your package for my tests, and I would be interested to accelerate the insertions of fixtures. What do you think of theses proposition ? Is it possible in this repo or another ?

First, to insert in batch. Instead of passing an object we could pass an array of objects to the function Factory.create, and it would insert in batch with an insertMany or somethink like this ? But I am not sure it is possible with meteor or if we can use a newer version of mongo just for the tests.

Second, I saw that in the factory.create : you findOne on the new document and return it. Sometime it is useful ( to get the id for example ) but sometime we could just pass it, with a flag or an other function ?

Factory._create = (name, doc) => {
  const collection = Factory.get(name).collection;
  const insertId = collection.insert(doc);
  // const record = collection.findOne(insertId); optional for an other function like fastICreate or with boolean and return nothing
  return record;
};

Array of Objects using Factory.get

I've the impression that this is yet another use case that it is not supported at the moment.
#15 and #14 seems related to this one, but not quite.

Running this define I get a RangeError: Maximum call stack size exceeded Error ...

Factory.define('fakeTeam',Volunteers.Collections.Teams,
  {
    'name': () -> faker.company.companyName()
    'visibility': 'public',
    'leads': () -> [ {'userId': Factory.get('fakeUser'), 'role': 'lead'} ]
    'tags': () -> [faker.lorem.words()],
    'parents':[],
  })

I can't find a better title to describe this problem ...

Document better what `.get` does

From the README.md:

Factory.get('name')
Returns the instance of name

For what I've tried, what .get does is creating an instance (just like create) but returning the _id of the instance instead of the instance itself.

Hard to deal with repeated objects

I'm having some problems with objects when the relations form a graph.

Consider this:

Factory.define "author", Author, { name: "F. Scott Fitzgerald" }
Factory.define "book", Book, { title: "The Great Gatsby": author: Factory.get("author") }
Factory.define "contract", Contract, { author: Factory.get("author"), book: Factory.get("book") }

When we Factory.create("contract"), we fail because there will be two inserts of author; one by contract and one when it creates the relation for book. We should only have one author object and the same id in both places. This can be fixed by after() hooks but it's a mess.

But, for this following case we might expect that a relation is different each time.

Factory.define "person", Person, { name: Random.string }
Factory.define "dance", Pair, { leader: Factory.get("person"), follower: Factory.get("person") }

It's easy to fix the first case by putting a name:object cache in Factory.build and using it to ensure that relations created unique objects. But that would break the latter case. Is the latter case a reasonable expectation? If not then I can probably build the cache and make a PR. If so then we need to figure out the best way to accommodate it.

Factory.define is not working!

if (Meteor.isServer) {
  describe('Workspaces Methods', function () {
    Factory.define('workspace', CollectionWorkSpaces, {
      owner: () => 'ABC',
      participant_ids: () => ['123'],
      invitee_emails: () => []
    });

    beforeEach(function () {
      resetDatabase();
    });

    it('updates invitee email list', function () {
      const document1 = Factory.create('workspace');
      console.log(document1); 
    })
  });
}
//output of document1
{
  _id: '7Qc9PTQpxCesqZS5k',
  owner: '3a6rW9QqnoTpMctCd',
  participant_ids: [],
  invitee_emails: []
}

//expected output:
{
  _id: '7Qc9PTQpxCesqZS5k',
  owner: 'ABC',
  participant_ids: ['123'],
  invitee_emails: []
}

Meteor version 2.5
dburles:factory version 1.1.0

Using factory with Mongo.ObjectID collections

My collections use Mongo.ObjectID's for _id generation.
export const Clients = new Mongo.Collection('clients', { idGeneration: 'MONGO' });

However, factory seems to default to a Random.Id() id generation which fails a lot of tests for me, so I tried adding id generation right into factory definition:
Factory.define('client', Clients, { _id: new Mongo.ObjectID(), //... });

This results in a mongo duplicate id error when I call Factory.create('client') three times in a row, though. For some reason all three clients end up with the same _id field.

Please advise.

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.