Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removes undefined values when populating #127

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

pdokas
Copy link

@pdokas pdokas commented Feb 23, 2018

Factory-girl does not support being passed an object spec where a member has the value undefined. This happens:

$ npm test -- test/utils/asyncPopulateSpec.js 

> factory-girl@5.0.1 test /Users/pdokas/dev/factory-girl
> NODE_ENV=test mocha "test/utils/asyncPopulateSpec.js"



  asyncPopulate
    ✓ returns a promise
    ✓ throws error if target or source is not an object
    1) populates objects correctly
    ✓ overrides only provided data


  3 passing (28ms)
  1 failing

  1) asyncPopulate populates objects correctly:
     TypeError: Cannot convert undefined or null to object
      at getPrototypeOf (<anonymous>)
      at isPlainObject (src/utils/asyncPopulate.js:35:10)
      at src/utils/asyncPopulate.js:19:16
      at Array.map (<anonymous>)
      at asyncPopulate (src/utils/asyncPopulate.js:10:40)
      at _callee3$ (test/utils/asyncPopulateSpec.js:62:11)
      at tryCatch (node_modules/regenerator-runtime/runtime.js:62:40)
      at Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:296:22)
      at Generator.prototype.(anonymous function) [as next] (node_modules/regenerator-runtime/runtime.js:114:21)
      at step (node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
      at node_modules/babel-runtime/helpers/asyncToGenerator.js:35:14
      at new Promise (<anonymous>)
      at new F (node_modules/core-js/library/modules/_export.js:35:28)
      at node_modules/babel-runtime/helpers/asyncToGenerator.js:14:12
      at _callee$ (test/test-helper/asyncFunction.js:4:11)
      at tryCatch (node_modules/regenerator-runtime/runtime.js:62:40)
      at Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:296:22)
      at Generator.prototype.(anonymous function) [as next] (node_modules/regenerator-runtime/runtime.js:114:21)
      at step (node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
      at node_modules/babel-runtime/helpers/asyncToGenerator.js:35:14
      at new Promise (<anonymous>)
      at new F (node_modules/core-js/library/modules/_export.js:35:28)
      at Context.<anonymous> (node_modules/babel-runtime/helpers/asyncToGenerator.js:14:12)
      at Context.<anonymous> (test/test-helper/asyncFunction.js:2:23)

If source[attr] is defined as undefined, then asyncPopulate calls isPlainObject which passes undefined to Object.getPrototypeOf which throws a TypeError.

This PR alleviates this by having factory-girl remove members where the value is undefined. A use case where this comes up is when using factory-girl with structure. Structure validates objects against a spec, and allows you to define default values for individual members.

So if I have an object spec and a factory for it like this:

module.exports = structure({
  is_followed: {
    type: Boolean,
    default: false,
  }
});

factory.define('person', exports, {
  is_followed: factory.chance('bool'),
});

This is great for tests, except when you want to test that the default works because there is no way to tell factory-girl "completely leave this one value out". And so, this PR alleviates this need by allowing someone to build a factory and tell it to drop a member from the built object.

@pdokas
Copy link
Author

pdokas commented Feb 23, 2018

P.S. I would love to document this in the readme, but I don’t see an obvious place to add the info. I’m absolutely open to suggestions!

@pdokas
Copy link
Author

pdokas commented Mar 10, 2018

@aexmachina: Hi there, I don't mean to pester but I was wondering if you have any thoughts about this. Thank you!

Copy link
Owner

@simonexmachina simonexmachina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, sorry for the late reply!

@@ -14,6 +14,8 @@ export default function asyncPopulate(target, source) {
promise = asyncPopulate(target[attr], source[attr]);
} else if (source[attr] === null) {
target[attr] = null;
} else if (typeof source[attr] === 'undefined') {
delete target[attr];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we delete this? I would've thought it'd be better to just pass through the undefined to the target.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The primary motivation here is to provide developers with a way of telling factory-girl to leave a particular attribute of the built object out. Without this all attributes are always required.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm against this in principle, unless there's a compelling reason why. Factories define a particular "type" of object, which should be stable at runtime. If you do need to omit a property at runtime then you can use afterBuild or some other hook to delete the property.

I'm still happy for this PR, because we want to avoid getPrototypeOf(undefined) throwing an exception, but I don't think we should delete any of the properties 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yeah, I see what you mean. That will probably work. I’ll give it a shot and report back.

Supposing passing through the value as undefined and then doing extra processing on it in afterBuild (in my case, deleting the key/value pair), would a diff like this be good to go?

+    } else if (typeof source[attr] === 'undefined') {
+      target[attr] = undefined;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants