diff options
author | Ghe Rivero <ghe@debian.org> | 2012-03-05 19:13:42 +0100 |
---|---|---|
committer | Ghe Rivero <ghe@debian.org> | 2012-03-05 19:13:42 +0100 |
commit | 176c017fe46396c2efe822220e8b1d874d022cb8 (patch) | |
tree | 531006702601e4e228c6a4fd196f9af308ae5b19 | |
parent | 491bd1262c2b9e4a4778bed0f8aa1a76244b3b7c (diff) | |
parent | b1682f428d29ea947604447c328b1a09a79313c0 (diff) | |
download | factory-boy-176c017fe46396c2efe822220e8b1d874d022cb8.tar factory-boy-176c017fe46396c2efe822220e8b1d874d022cb8.tar.gz |
Merge tag 'v1.1.2' into debian/unstable
Release of factory_boy 1.1.2
-rw-r--r-- | README | 223 | ||||
l---------[-rw-r--r--] | README.rst | 223 | ||||
-rw-r--r-- | docs/conf.py | 3 | ||||
-rw-r--r-- | docs/examples.rst | 136 | ||||
-rw-r--r-- | docs/index.rst | 6 | ||||
-rw-r--r-- | docs/internals.rst | 25 | ||||
-rw-r--r-- | docs/subfactory.rst | 56 | ||||
-rw-r--r-- | factory/__init__.py | 22 | ||||
-rw-r--r-- | factory/base.py | 203 | ||||
-rw-r--r-- | factory/containers.py | 67 | ||||
-rw-r--r-- | factory/declarations.py | 140 | ||||
-rw-r--r-- | setup.py | 6 | ||||
-rw-r--r-- | tests/__init__.py | 1 | ||||
-rw-r--r-- | tests/test_base.py | 353 | ||||
-rw-r--r-- | tests/test_containers.py | 44 | ||||
-rw-r--r-- | tests/test_declarations.py | 28 | ||||
-rw-r--r-- | tests/test_using.py | 810 |
17 files changed, 1777 insertions, 569 deletions
@@ -0,0 +1,223 @@ +factory_boy +=========== + +factory_boy is a fixtures replacement based on thoughtbot's `factory_girl <http://github.com/thoughtbot/factory_girl>`_ . Like factory_girl it has a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute dicts, and stubbed objects), and support for multiple factories for the same class, including factory inheritance. Django support is included, and support for other ORMs can be easily added. + +The official repository is at http://github.com/rbarrois/factory_boy. + +Credits +------- + +This README parallels the factory_girl README as much as possible; text and examples are reproduced for comparison purposes. Ruby users of factory_girl should feel right at home with factory_boy in Python. + +factory_boy was originally written by Mark Sandstrom, and improved by Raphaël Barrois. + +Thank you Joe Ferris and thoughtbot for creating factory_girl. + +Download +-------- + +Github: http://github.com/rbarrois/factory_boy/ + +PyPI:: + + pip install factory_boy + +Source:: + + # Download the source and run + python setup.py install + + +Defining factories +------------------ + +Factories declare a set of attributes used to instantiate an object. The class of the object must be defined in the FACTORY_FOR attribute:: + + import factory + from models import User + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + first_name = 'John' + last_name = 'Doe' + admin = False + + # Another, different, factory for the same object + class AdminFactory(factory.Factory): + FACTORY_FOR = User + + first_name = 'Admin' + last_name = 'User' + admin = True + +Using factories +--------------- + +factory_boy supports several different build strategies: build, create, attributes and stub:: + + # Returns a User instance that's not saved + user = UserFactory.build() + + # Returns a saved User instance + user = UserFactory.create() + + # Returns a dict of attributes that can be used to build a User instance + attributes = UserFactory.attributes() + + # Returns an object with all defined attributes stubbed out: + stub = UserFactory.stub() + +You can use the Factory class as a shortcut for the default build strategy:: + + # Same as UserFactory.create() + user = UserFactory() + +The default strategy can be overridden:: + + UserFactory.default_strategy = factory.BUILD_STRATEGY + user = UserFactory() + +The default strategy can also be overridden for all factories:: + + # This will set the default strategy for all factories that don't define a default build strategy + factory.Factory.default_strategy = factory.BUILD_STRATEGY + +No matter which strategy is used, it's possible to override the defined attributes by passing keyword arguments:: + + # Build a User instance and override first_name + user = UserFactory.build(first_name='Joe') + user.first_name + # => 'Joe' + +Lazy Attributes +--------------- + +Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as associations and other attributes that must be dynamically generated) will need values assigned each time an instance is generated. These "lazy" attributes can be added as follows:: + + class UserFactory(factory.Factory): + first_name = 'Joe' + last_name = 'Blow' + email = factory.LazyAttribute(lambda a: '{0}.{1}@example.com'.format(a.first_name, a.last_name).lower()) + + UserFactory().email + # => 'joe.blow@example.com' + +The function passed to ``LazyAttribute`` is given the attributes defined for the factory up to the point of the LazyAttribute declaration. If a lambda won't cut it, the ``lazy_attribute`` decorator can be used to wrap a function:: + + # Stub factories don't have an associated class. + class SumFactory(factory.StubFactory): + lhs = 1 + rhs = 1 + + @lazy_attribute + def sum(a): + result = a.lhs + a.rhs # Or some other fancy calculation + return result + +Associations +------------ + +Associated instances can also be generated using ``LazyAttribute``:: + + from models import Post + + class PostFactory(factory.Factory): + author = factory.LazyAttribute(lambda a: UserFactory()) + +The associated object's default strategy is always used:: + + # Builds and saves a User and a Post + post = PostFactory() + post.id == None # => False + post.author.id == None # => False + + # Builds and saves a User, and then builds but does not save a Post + post = PostFactory.build() + post.id == None # => True + post.author.id == None # => False + +Inheritance +----------- + +You can easily create multiple factories for the same class without repeating common attributes by using inheritance:: + + class PostFactory(factory.Factory): + title = 'A title' + + class ApprovedPost(PostFactory): + approved = True + approver = factory.LazyAttribute(lambda a: UserFactory()) + +Sequences +--------- + +Unique values in a specific format (for example, e-mail addresses) can be generated using sequences. Sequences are defined by using ``Sequence`` or the decorator ``sequence``:: + + class UserFactory(factory.Factory): + email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n)) + + UserFactory().email # => 'person0@example.com' + UserFactory().email # => 'person1@example.com' + +Sequences can be combined with lazy attributes:: + + class UserFactory(factory.Factory): + name = 'Mark' + email = factory.LazyAttributeSequence(lambda a, n: '{0}+{1}@example.com'.format(a.name, n).lower()) + + UserFactory().email # => mark+0@example.com + +If you wish to use a custom method to set the initial ID for a sequence, you can override the ``_setup_next_sequence`` class method:: + + class MyFactory(factory.Factory): + + @classmethod + def _setup_next_sequence(cls): + return cls._associated_class.objects.values_list('id').order_by('-id')[0] + 1 + +Customizing creation +-------------------- + +Sometimes, the default build/create by keyword arguments doesn't allow for enough +customization of the generated objects. In such cases, you should override the +Factory._prepare method:: + + class UserFactory(factory.Factory): + @classmethod + def _prepare(cls, create, **kwargs): + password = kwargs.pop('password', None) + user = super(UserFactory, cls)._prepare(create, **kwargs) + if password: + user.set_password(password) + if create: + user.save() + return user + +Subfactories +------------ + +If one of your factories has a field which is another factory, you can declare it as a ``SubFactory``. This allows to define attributes of that field when calling +the global factory, using a simple syntax : ``field__attr=42`` will set the attribute ``attr`` of the ``SubFactory`` defined in ``field`` to 42:: + + class InnerFactory(factory.Factory): + foo = 'foo' + bar = factory.LazyAttribute(lambda o: foo * 2) + + class ExternalFactory(factory.Factory): + inner = factory.SubFactory(InnerFactory, foo='bar') + + >>> e = ExternalFactory() + >>> e.foo + 'bar' + >>> e.bar + 'barbar' + + >>> e2 : ExternalFactory(inner__bar='baz') + >>> e2.foo + 'bar' + >>> e2.bar + 'baz' + + diff --git a/README.rst b/README.rst index dba0dbe..100b938 100644..120000 --- a/README.rst +++ b/README.rst @@ -1,222 +1 @@ -factory_boy -=========== - -factory_boy is a fixtures replacement based on thoughtbot's `factory_girl <http://github.com/thoughtbot/factory_girl>`_ . Like factory_girl it has a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute dicts, and stubbed objects), and support for multiple factories for the same class, including factory inheritance. Django support is included, and support for other ORMs can be easily added. - -The official repository is at http://github.com/rbarrois/factory_boy. - -Credits -------- - -This README parallels the factory_girl README as much as possible; text and examples are reproduced for comparison purposes. Ruby users of factory_girl should feel right at home with factory_boy in Python. - -factory_boy was originally written by Mark Sandstrom, and improved by Raphaël Barrois. - -Thank you Joe Ferris and thoughtbot for creating factory_girl. - -Download --------- - -Github: http://github.com/rbarrois/factory_boy/ - -easy_install:: - - easy_install factory_boy - -Source:: - - # Download the source and run - python setup.py install - - -Defining factories ------------------- - -Factories declare a set of attributes used to instantiate an object. The name of the factory is used to guess the class of the object by default, but it's possible to explicitly specify it:: - - import factory - from models import User - - # This will guess the User class - class UserFactory(factory.Factory): - first_name = 'John' - last_name = 'Doe' - admin = False - - # This will use the User class (Admin would have been guessed) - class AdminFactory(factory.Factory): - FACTORY_FOR = User - - first_name = 'Admin' - last_name = 'User' - admin = True - -Using factories ---------------- - -factory_boy supports several different build strategies: build, create, attributes and stub:: - - # Returns a User instance that's not saved - user = UserFactory.build() - - # Returns a saved User instance - user = UserFactory.create() - - # Returns a dict of attributes that can be used to build a User instance - attributes = UserFactory.attributes() - - # Returns an object with all defined attributes stubbed out: - stub = UserFactory.stub() - -You can use the Factory class as a shortcut for the default build strategy:: - - # Same as UserFactory.create() - user = UserFactory() - -The default strategy can be overridden:: - - UserFactory.default_strategy = factory.BUILD_STRATEGY - user = UserFactory() - -The default strategy can also be overridden for all factories:: - - # This will set the default strategy for all factories that don't define a default build strategy - factory.Factory.default_strategy = factory.BUILD_STRATEGY - -No matter which strategy is used, it's possible to override the defined attributes by passing keyword arguments:: - - # Build a User instance and override first_name - user = UserFactory.build(first_name='Joe') - user.first_name - # => 'Joe' - -Lazy Attributes ---------------- - -Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as associations and other attributes that must be dynamically generated) will need values assigned each time an instance is generated. These "lazy" attributes can be added as follows:: - - class UserFactory(factory.Factory): - first_name = 'Joe' - last_name = 'Blow' - email = factory.LazyAttribute(lambda a: '{0}.{1}@example.com'.format(a.first_name, a.last_name).lower()) - - UserFactory().email - # => 'joe.blow@example.com' - -The function passed to ``LazyAttribute`` is given the attributes defined for the factory up to the point of the LazyAttribute declaration. If a lambda won't cut it, the ``lazy_attribute`` decorator can be used to wrap a function:: - - # Stub factories don't have an associated class. - class SumFactory(factory.StubFactory): - lhs = 1 - rhs = 1 - - @lazy_attribute - def sum(a): - result = a.lhs + a.rhs # Or some other fancy calculation - return result - -Associations ------------- - -Associated instances can also be generated using ``LazyAttribute``:: - - from models import Post - - class PostFactory(factory.Factory): - author = factory.LazyAttribute(lambda a: UserFactory()) - -The associated object's default strategy is always used:: - - # Builds and saves a User and a Post - post = PostFactory() - post.id == None # => False - post.author.id == None # => False - - # Builds and saves a User, and then builds but does not save a Post - post = PostFactory.build() - post.id == None # => True - post.author.id == None # => False - -Inheritance ------------ - -You can easily create multiple factories for the same class without repeating common attributes by using inheritance:: - - class PostFactory(factory.Factory): - title = 'A title' - - class ApprovedPost(PostFactory): - approved = True - approver = factory.LazyAttribute(lambda a: UserFactory()) - -Sequences ---------- - -Unique values in a specific format (for example, e-mail addresses) can be generated using sequences. Sequences are defined by using ``Sequence`` or the decorator ``sequence``:: - - class UserFactory(factory.Factory): - email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n)) - - UserFactory().email # => 'person0@example.com' - UserFactory().email # => 'person1@example.com' - -Sequences can be combined with lazy attributes:: - - class UserFactory(factory.Factory): - name = 'Mark' - email = factory.LazyAttributeSequence(lambda a, n: '{0}+{1}@example.com'.format(a.name, n).lower()) - - UserFactory().email # => mark+0@example.com - -If you wish to use a custom method to set the initial ID for a sequence, you can override the ``_setup_next_sequence`` class method:: - - class MyFactory(factory.Factory): - - @classmethod - def _setup_next_sequence(cls): - return cls._associated_class.objects.values_list('id').order_by('-id')[0] + 1 - -Customizing creation --------------------- - -Sometimes, the default build/create by keyword arguments doesn't allow for enough -customization of the generated objects. In such cases, you should override the -Factory._prepare method:: - - class UserFactory(factory.Factory): - @classmethod - def _prepare(cls, create, **kwargs): - password = kwargs.pop('password', None) - user = super(UserFactory, cls)._prepare(create, kwargs) - if password: - user.set_password(user) - if create: - user.save() - return user - -Subfactories ------------- - -If one of your factories has a field which is another factory, you can declare it as a ``SubFactory``. This allows to define attributes of that field when calling -the global factory, using a simple syntax : ``field__attr=42`` will set the attribute ``attr`` of the ``SubFactory`` defined in ``field`` to 42:: - - class InnerFactory(factory.Factory): - foo = 'foo' - bar = factory.LazyAttribute(lambda o: foo * 2) - - class ExternalFactory(factory.Factory): - inner = factory.SubFactory(InnerFactory, foo='bar') - - >>> e = ExternalFactory() - >>> e.foo - 'bar' - >>> e.bar - 'barbar' - - >>> e2 : ExternalFactory(inner__bar='baz') - >>> e2.foo - 'bar' - >>> e2.bar - 'baz' - - +README
\ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index f50b1a6..db523c3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,7 @@ import sys, os # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.dirname(os.path.abspath('.'))) # -- General configuration ----------------------------------------------------- @@ -50,7 +51,7 @@ copyright = u'2011, Raphaël Barrois, Mark Sandstrom' # The short X.Y version. version = '1.1' # The full version, including alpha/beta/rc tags. -release = '1.1.0' +release = '1.1.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 0000000..cac6bc6 --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,136 @@ +Examples +======== + +Here are some real-world examples of using FactoryBoy. + + +Objects +------- + +First, let's define a couple of objects:: + + class Account(object): + def __init__(self, username, email): + self.username = username + self.email = email + + def __str__(self): + return '%s (%s)' % (self.username, self.email) + + + class Profile(object): + + GENDER_MALE = 'm' + GENDER_FEMALE = 'f' + GENDER_UNKNOWN = 'u' # If the user refused to give it + + def __init__(self, account, gender, firstname, lastname, planet='Earth'): + self.account = account + self.gender = gender + self.firstname = firstname + self.lastname = lastname + self.planet = planet + + def __unicode__(self): + return u'%s %s (%s)' % ( + unicode(self.firstname), + unicode(self.lastname), + unicode(self.account.accountname), + ) + +Factories +--------- + +And now, we'll define the related factories:: + + import factory + import random + + from . import objects + + + class AccountFactory(factory.Factory): + FACTORY_FOR = objects.Account + + username = factory.Sequence(lambda n: 'john%s' % n) + email = factory.LazyAttribute(lambda o: '%s@example.org' % o.username) + + + class ProfileFactory(factory.Factory): + FACTORY_FOR = objects.Profile + + account = factory.SubFactory(AccountFactory) + gender = random.choice([objects.Profile.GENDER_MALE, objects.Profile.GENDER_FEMALE]) + firstname = u'John' + lastname = u'Doe' + + + +We have now defined basic factories for our :py:class:`~Account` and :py:class:`~Profile` classes. + +If we commonly use a specific variant of our objects, we can refine a factory accordingly:: + + class FemaleProfileFactory(ProfileFactory): + gender = objects.Profile.GENDER_FEMALE + firstname = u'Jane' + user__username = factory.Sequence(lambda n: 'jane%s' % n) + + + +Using the factories +------------------- + +We can now use our factories, for tests:: + + import unittest + + from . import business_logic + from . import factories + from . import objects + + + class MyTestCase(unittest.TestCase): + + def test_send_mail(self): + account = factories.AccountFactory() + email = business_logic.prepare_email(account, subject='Foo', text='Bar') + + self.assertEqual(email.to, account.email) + + def test_get_profile_stats(self): + profiles = [] + + for _ in xrange(4): + profiles.append(factories.ProfileFactory()) + for _ in xrange(2): + profiles.append(factories.FemaleProfileFactory()) + for _ in xrange(2): + profiles.append(factories.ProfileFactory(planet='Tatooine')) + + stats = business_logic.profile_stats(profiles) + self.assertEqual({'Earth': 6, 'Mars': 2}, stats.planets) + self.assertLess(stats.genders[objects.Profile.GENDER_FEMALE], 2) + + +Or for fixtures:: + + from . import factories + + def make_objects(): + for _ in xrange(50): + factories.ProfileFactory() + + # Let's create a few, known objects. + factories.ProfileFactory( + gender=objects.Profile.GENDER_MALE, + firstname='Luke', + lastname='Skywalker', + planet='Tatooine', + ) + + factories.ProfileFactory( + gender=objects.Profile.GENDER_FEMALE, + firstname='Leia', + lastname='Organa', + planet='Alderaan', + ) diff --git a/docs/index.rst b/docs/index.rst index 69b3b99..eff5ac6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -55,7 +55,11 @@ Once defined, a factory can be instantiated through different methods:: Contents: .. toctree:: - :maxdepth: 2 + :maxdepth: 2 + + examples + subfactory + internals Indices and tables ================== diff --git a/docs/internals.rst b/docs/internals.rst new file mode 100644 index 0000000..75de63d --- /dev/null +++ b/docs/internals.rst @@ -0,0 +1,25 @@ +Factoy Boy's internals +====================== + + +declarations +------------ + +.. automodule:: factory.declarations + :members: + + +containers +---------- + +.. automodule:: factory.containers + :members: + + + +base +---- + +.. automodule:: factory.base + :members: + diff --git a/docs/subfactory.rst b/docs/subfactory.rst new file mode 100644 index 0000000..8bc1d8f --- /dev/null +++ b/docs/subfactory.rst @@ -0,0 +1,56 @@ +SubFactory +========== + +Some objects may use other complex objects as parameters; in order to simplify this setup, factory_boy +provides the :py:class:`factory.SubFactory` class. + +This should be used when defining a :py:class:`~factory.Factory` attribute that will hold the other complex object:: + + import factory + + # A standard factory + class UserFactory(factory.Factory): + FACTORY_FOR = User + + # Various fields + first_name = 'John' + last_name = factory.Sequence(lambda n: 'D%se' % (o * n)) + email = factory.LazyAttribute(lambda o: '%s.%s@example.org' % (o.first_name.lower(), o.last_name.lower())) + + # A factory for an object with a 'User' field + class CompanyFactory(factory.Factory): + FACTORY_FOR = Company + + name = factory.Sequence(lambda n: 'FactoryBoyz' + z * n) + + # Let's use our UserFactory to create that user, and override its first name. + owner = factory.SubFactory(UserFactory, first_name='Jack') + +Instantiating the external factory will in turn instantiate an object of the internal factory:: + + >>> c = CompanyFactory() + >>> c + <Company: FactoryBoyz> + + # Notice that the first_name was overridden + >>> c.owner + <User: Jack De> + >>> c.owner.email + jack.de@example.org + +Fields of the SubFactory can also be overridden when instantiating the external factory:: + + >>> c = CompanyFactory(owner__first_name='Henry') + >>> c.owner + <User: Henry Doe> + + # Notice that the updated first_name was propagated to the email LazyAttribute. + >>> c.owner.email + henry.doe@example.org + + # It is also possible to override other fields of the SubFactory + >>> c = CompanyFactory(owner__last_name='Jones') + >>> c.owner + <User: Henry Jones> + >>> c.owner.email + henry.jones@example.org diff --git a/factory/__init__.py b/factory/__init__.py index efc0001..1d4408f 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -__version__ = '1.0.4' # Remember to change in setup.py as well! +__version__ = '1.1.2' # Remember to change in setup.py as well! __author__ = 'Raphaël Barrois <raphael.barrois@polytechnique.org>' from base import ( @@ -28,6 +28,19 @@ from base import ( StubFactory, DjangoModelFactory, + build, + create, + stub, + generate, + simple_generate, + make_factory, + + build_batch, + create_batch, + stub_batch, + generate_batch, + simple_generate_batch, + BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY, @@ -39,12 +52,19 @@ from base import ( from declarations import ( LazyAttribute, + Iterator, + InfiniteIterator, Sequence, LazyAttributeSequence, + SelfAttribute, + ContainerAttribute, SubFactory, lazy_attribute, + iterator, + infinite_iterator, sequence, lazy_attribute_sequence, + container_attribute, ) diff --git a/factory/base.py b/factory/base.py index 3f3261b..62131fb 100644 --- a/factory/base.py +++ b/factory/base.py @@ -22,6 +22,7 @@ import re import sys +import warnings from factory import containers @@ -156,28 +157,36 @@ class FactoryMetaClass(BaseFactoryMetaClass): if FACTORY_CLASS_DECLARATION in attrs: return attrs[FACTORY_CLASS_DECLARATION] - factory_module = sys.modules[attrs['__module__']] - if class_name.endswith('Factory'): - # Try a module lookup - used_auto_discovery = True - associated_class_name = class_name[:-len('Factory')] - if associated_class_name: - # Class name was longer than just 'Factory'. - try: - return getattr(factory_module, associated_class_name) - except AttributeError: - pass - - # Unable to guess a good option; return the inherited class. + # No specific associated calss was given, and one was defined for our + # parent, use it. if inherited is not None: return inherited + if '__module__' in attrs: + factory_module = sys.modules[attrs['__module__']] + if class_name.endswith('Factory'): + # Try a module lookup + used_auto_discovery = True + associated_name = class_name[:-len('Factory')] + if associated_name and hasattr(factory_module, associated_name): + warnings.warn( + "Auto-discovery of associated class is deprecated, and " + "will be removed in the future. Please set '%s = %s' " + "in the %s class definition." % ( + FACTORY_CLASS_DECLARATION, + associated_name, + class_name, + ), PendingDeprecationWarning) + + return getattr(factory_module, associated_name) + + # Unable to guess a good option; return the inherited class. # Unable to find an associated class; fail. if used_auto_discovery: raise Factory.AssociatedClassError( FactoryMetaClass.ERROR_MESSAGE_AUTODISCOVERY.format( FACTORY_CLASS_DECLARATION, - associated_class_name, + associated_name, class_name, factory_module,)) else: @@ -300,11 +309,35 @@ class BaseFactory(object): raise cls.UnsupportedStrategy() @classmethod + def build_batch(cls, size, **kwargs): + """Build a batch of instances of the given class, with overriden attrs. + + Args: + size (int): the number of instances to build + + Returns: + object list: the built instances + """ + return [cls.build(**kwargs) for _ in xrange(size)] + + @classmethod def create(cls, **kwargs): """Create an instance of the associated class, with overriden attrs.""" raise cls.UnsupportedStrategy() @classmethod + def create_batch(cls, size, **kwargs): + """Create a batch of instances of the given class, with overriden attrs. + + Args: + size (int): the number of instances to create + + Returns: + object list: the created instances + """ + return [cls.create(**kwargs) for _ in xrange(size)] + + @classmethod def stub(cls, **kwargs): """Retrieve a stub of the associated class, with overriden attrs. @@ -316,6 +349,84 @@ class BaseFactory(object): setattr(stub_object, name, value) return stub_object + @classmethod + def stub_batch(cls, size, **kwargs): + """Stub a batch of instances of the given class, with overriden attrs. + + Args: + size (int): the number of instances to stub + + Returns: + object list: the stubbed instances + """ + return [cls.stub(**kwargs) for _ in xrange(size)] + + @classmethod + def generate(cls, strategy, **kwargs): + """Generate a new instance. + + The instance will be created with the given strategy (one of + BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY). + + Args: + strategy (str): the strategy to use for generating the instance. + + Returns: + object: the generated instance + """ + assert strategy in (STUB_STRATEGY, BUILD_STRATEGY, CREATE_STRATEGY) + action = getattr(cls, strategy) + return action(**kwargs) + + @classmethod + def generate_batch(cls, strategy, size, **kwargs): + """Generate a batch of instances. + + The instances will be created with the given strategy (one of + BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY). + + Args: + strategy (str): the strategy to use for generating the instance. + size (int): the number of instances to generate + + Returns: + object list: the generated instances + """ + assert strategy in (STUB_STRATEGY, BUILD_STRATEGY, CREATE_STRATEGY) + batch_action = getattr(cls, '%s_batch' % strategy) + return batch_action(size, **kwargs) + + @classmethod + def simple_generate(cls, create, **kwargs): + """Generate a new instance. + + The instance will be either 'built' or 'created'. + + Args: + create (bool): whether to 'build' or 'create' the instance. + + Returns: + object: the generated instance + """ + strategy = CREATE_STRATEGY if create else BUILD_STRATEGY + return cls.generate(strategy, **kwargs) + + @classmethod + def simple_generate_batch(cls, create, size, **kwargs): + """Generate a batch of instances. + + These instances will be either 'built' or 'created'. + + Args: + size (int): the number of instances to generate + create (bool): whether to 'build' or 'create' the instances. + + Returns: + object list: the generated instances + """ + strategy = CREATE_STRATEGY if create else BUILD_STRATEGY + return cls.generate_batch(strategy, size, **kwargs) + class StubFactory(BaseFactory): __metaclass__ = BaseFactoryMetaClass @@ -340,6 +451,10 @@ class Factory(BaseFactory): # from turning it into an instance method. _creation_function = (DJANGO_CREATION,) + def __str__(self): + return '<%s for %s>' % (self.__class__.__name__, + getattr(self, CLASS_ATTRIBUTE_ASSOCIATED_CLASS).__name__) + @classmethod def set_creation_function(cls, creation_function): """Set the creation function for this class. @@ -439,3 +554,63 @@ class DjangoModelFactory(Factory): ).order_by('-id')[0] except IndexError: return 1 + + +def make_factory(klass, **kwargs): + """Create a new, simple factory for the given class.""" + factory_name = '%sFactory' % klass.__name__ + kwargs[FACTORY_CLASS_DECLARATION] = klass + factory_class = type(Factory).__new__(type(Factory), factory_name, (Factory,), kwargs) + factory_class.__name__ = '%sFactory' % klass.__name__ + factory_class.__doc__ = 'Auto-generated factory for class %s' % klass + return factory_class + + +def build(klass, **kwargs): + """Create a factory for the given class, and build an instance.""" + return make_factory(klass, **kwargs).build() + + +def build_batch(klass, size, **kwargs): + """Create a factory for the given class, and build a batch of instances.""" + return make_factory(klass, **kwargs).build_batch(size) + + +def create(klass, **kwargs): + """Create a factory for the given class, and create an instance.""" + return make_factory(klass, **kwargs).create() + + +def create_batch(klass, size, **kwargs): + """Create a factory for the given class, and create a batch of instances.""" + return make_factory(klass, **kwargs).create_batch(size) + + +def stub(klass, **kwargs): + """Create a factory for the given class, and stub an instance.""" + return make_factory(klass, **kwargs).stub() + + +def stub_batch(klass, size, **kwargs): + """Create a factory for the given class, and stub a batch of instances.""" + return make_factory(klass, **kwargs).stub_batch(size) + + +def generate(klass, strategy, **kwargs): + """Create a factory for the given class, and generate an instance.""" + return make_factory(klass, **kwargs).generate(strategy) + + +def generate_batch(klass, strategy, size, **kwargs): + """Create a factory for the given class, and generate instances.""" + return make_factory(klass, **kwargs).generate_batch(strategy, size) + + +def simple_generate(klass, create, **kwargs): + """Create a factory for the given class, and simple_generate an instance.""" + return make_factory(klass, **kwargs).simple_generate(create) + + +def simple_generate_batch(klass, create, size, **kwargs): + """Create a factory for the given class, and simple_generate instances.""" + return make_factory(klass, **kwargs).simple_generate_batch(create, size) diff --git a/factory/containers.py b/factory/containers.py index e280162..2f92f62 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -45,16 +45,29 @@ class LazyStub(object): __values (dict): maps attribute name to computed value __pending (str list): names of the attributes whose value is being computed. This allows to detect cyclic lazy attribute definition. + __containers (LazyStub list): "parents" of the LazyStub being built. + This allows to have the field of a field depend on the value of + another field + __target_class (type): the target class to build. """ __initialized = False - def __init__(self, attrs): + def __init__(self, attrs, containers=(), target_class=object): self.__attrs = attrs self.__values = {} self.__pending = [] + self.__containers = containers + self.__target_class = target_class self.__initialized = True + def __repr__(self): + return '<LazyStub for %s>' % self.__target_class.__name__ + + def __str__(self): + return '<LazyStub for %s with %s>' % ( + self.__target_class.__name__, self.__attrs.keys()) + def __fill__(self): """Fill this LazyStub, computing values of all defined attributes. @@ -82,7 +95,7 @@ class LazyStub(object): val = self.__attrs[name] if isinstance(val, LazyValue): self.__pending.append(name) - val = val.evaluate(self) + val = val.evaluate(self, self.__containers) assert name == self.__pending.pop() self.__values[name] = val return val @@ -103,20 +116,35 @@ class LazyStub(object): class DeclarationDict(dict): """Slightly extended dict to work with OrderedDeclaration.""" + def is_declaration(self, name, value): + """Determines if a class attribute is a field value declaration. + + Based on the name and value of the class attribute, return ``True`` if + it looks like a declaration of a default field value, ``False`` if it + is private (name starts with '_') or a classmethod or staticmethod. + + """ + if isinstance(value, (classmethod, staticmethod)): + return False + elif isinstance(value, declarations.OrderedDeclaration): + return True + return (not name.startswith("_")) + def update_with_public(self, d): """Updates the DeclarationDict from a class definition dict. Takes into account all public attributes and OrderedDeclaration - instances; ignores all attributes starting with '_'. + instances; ignores all class/staticmethods and private attributes + (starting with '_'). Returns a dict containing all remaining elements. """ remaining = {} for k, v in d.iteritems(): - if k.startswith('_') and not isinstance(v, declarations.OrderedDeclaration): - remaining[k] = v - else: + if self.is_declaration(k, v): self[k] = v + else: + remaining[k] = v return remaining def copy(self, extra=None): @@ -135,7 +163,7 @@ class DeclarationDict(dict): class LazyValue(object): """Some kind of "lazy evaluating" object.""" - def evaluate(self, obj): + def evaluate(self, obj, containers=()): """Compute the value, using the given object.""" raise NotImplementedError("This is an abstract method.") @@ -156,8 +184,12 @@ class SubFactoryWrapper(LazyValue): self.subfields = subfields self.create = create - def evaluate(self, obj): - return self.subfactory.evaluate(self.create, self.subfields) + def evaluate(self, obj, containers=()): + expanded_containers = (obj,) + if containers: + expanded_containers += tuple(containers) + return self.subfactory.evaluate(self.create, self.subfields, + expanded_containers) class OrderedDeclarationWrapper(LazyValue): @@ -175,8 +207,15 @@ class OrderedDeclarationWrapper(LazyValue): self.declaration = declaration self.sequence = sequence - def evaluate(self, obj): - return self.declaration.evaluate(self.sequence, obj) + def evaluate(self, obj, containers=()): + """Lazily evaluate the attached OrderedDeclaration. + + Args: + obj (LazyStub): the object being built + containers (object list): the chain of containers of the object + being built, its immediate holder being first. + """ + return self.declaration.evaluate(self.sequence, obj, containers) class AttributeBuilder(object): @@ -195,7 +234,9 @@ class AttributeBuilder(object): if not extra: extra = {} + self.factory = factory + self._containers = extra.pop('__containers', None) self._attrs = factory.declarations(extra) self._subfields = self._extract_subfields() @@ -229,7 +270,9 @@ class AttributeBuilder(object): v = OrderedDeclarationWrapper(v, self.factory.sequence) wrapped_attrs[k] = v - return LazyStub(wrapped_attrs).__fill__() + stub = LazyStub(wrapped_attrs, containers=self._containers, + target_class=self.factory) + return stub.__fill__() class StubObject(object): diff --git a/factory/declarations.py b/factory/declarations.py index 4a5bf97..41d99a3 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -20,6 +20,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. + +import itertools + + class OrderedDeclaration(object): """A factory declaration. @@ -28,7 +32,7 @@ class OrderedDeclaration(object): in the same factory. """ - def evaluate(self, sequence, obj): + def evaluate(self, sequence, obj, containers=()): """Evaluate this declaration. Args: @@ -36,6 +40,8 @@ class OrderedDeclaration(object): the current instance obj (containers.LazyStub): The object holding currently computed attributes + containers (list of containers.LazyStub): The chain of SubFactory + which led to building this object. """ raise NotImplementedError('This is an abstract method') @@ -52,25 +58,87 @@ class LazyAttribute(OrderedDeclaration): super(LazyAttribute, self).__init__(*args, **kwargs) self.function = function - def evaluate(self, sequence, obj): + def evaluate(self, sequence, obj, containers=()): return self.function(obj) +class _UNSPECIFIED(object): + pass + + +def deepgetattr(obj, name, default=_UNSPECIFIED): + """Try to retrieve the given attribute of an object, digging on '.'. + + This is an extended getattr, digging deeper if '.' is found. + + Args: + obj (object): the object of which an attribute should be read + name (str): the name of an attribute to look up. + default (object): the default value to use if the attribute wasn't found + + Returns: + the attribute pointed to by 'name', splitting on '.'. + + Raises: + AttributeError: if obj has no 'name' attribute. + """ + try: + if '.' in name: + attr, subname = name.split('.', 1) + return deepgetattr(getattr(obj, attr), subname, default) + else: + return getattr(obj, name) + except AttributeError: + if default is _UNSPECIFIED: + raise + else: + return default + + class SelfAttribute(OrderedDeclaration): """Specific OrderedDeclaration copying values from other fields. Attributes: attribute_name (str): the name of the attribute to copy. + default (object): the default value to use if the attribute doesn't + exist. """ - def __init__(self, attribute_name, *args, **kwargs): + def __init__(self, attribute_name, default=_UNSPECIFIED, *args, **kwargs): super(SelfAttribute, self).__init__(*args, **kwargs) self.attribute_name = attribute_name + self.default = default + + def evaluate(self, sequence, obj, containers=()): + return deepgetattr(obj, self.attribute_name, self.default) + + +class Iterator(OrderedDeclaration): + """Fill this value using the values returned by an iterator. + + Warning: the iterator should not end ! + + Attributes: + iterator (iterable): the iterator whose value should be used. + """ + + def __init__(self, iterator): + super(Iterator, self).__init__() + self.iterator = iter(iterator) + + def evaluate(self, sequence, obj, containers=()): + return self.iterator.next() - def evaluate(self, sequence, obj): - # TODO(rbarrois): allow the use of ATTR_SPLITTER to fetch fields of - # subfactories. - return getattr(obj, self.attribute_name) + +class InfiniteIterator(Iterator): + """Same as Iterator, but make the iterator infinite by cycling at the end. + + Attributes: + iterator (iterable): the iterator, once made infinite. + """ + + def __init__(self, iterator): + return super(InfiniteIterator, self).__init__(itertools.cycle(iterator)) class Sequence(OrderedDeclaration): @@ -89,7 +157,7 @@ class Sequence(OrderedDeclaration): self.function = function self.type = type - def evaluate(self, sequence, obj): + def evaluate(self, sequence, obj, containers=()): return self.function(self.type(sequence)) @@ -102,10 +170,42 @@ class LazyAttributeSequence(Sequence): type (function): A function converting an integer into the expected kind of counter for the 'function' attribute. """ - def evaluate(self, sequence, obj): + def evaluate(self, sequence, obj, containers=()): return self.function(obj, self.type(sequence)) +class ContainerAttribute(OrderedDeclaration): + """Variant of LazyAttribute, also receives the containers of the object. + + Attributes: + function (function): A function, expecting the current LazyStub and the + (optional) object having a subfactory containing this attribute. + strict (bool): Whether evaluating should fail when the containers are + not passed in (i.e used outside a SubFactory). + """ + def __init__(self, function, strict=True, *args, **kwargs): + super(ContainerAttribute, self).__init__(*args, **kwargs) + self.function = function + self.strict = strict + + def evaluate(self, sequence, obj, containers=()): + """Evaluate the current ContainerAttribute. + + Args: + obj (LazyStub): a lazy stub of the object being constructed, if + needed. + containers (list of LazyStub): a list of lazy stubs of factories + being evaluated in a chain, each item being a future field of + next one. + """ + if self.strict and not containers: + raise TypeError( + "A ContainerAttribute in 'strict' mode can only be used " + "within a SubFactory.") + + return self.function(obj, containers) + + class SubFactory(OrderedDeclaration): """Base class for attributes based upon a sub-factory. @@ -120,18 +220,27 @@ class SubFactory(OrderedDeclaration): self.defaults = kwargs self.factory = factory - def evaluate(self, create, extra): + def evaluate(self, create, extra, containers): """Evaluate the current definition and fill its attributes. Uses attributes definition in the following order: - attributes defined in the wrapped factory class - values defined when defining the SubFactory - additional values defined in attributes + + Args: + create (bool): whether the subfactory should call 'build' or + 'create' + extra (containers.DeclarationDict): extra values that should + override the wrapped factory's defaults + containers (list of LazyStub): List of LazyStub for the chain of + factories being evaluated, the calling stub being first. """ defaults = dict(self.defaults) if extra: defaults.update(extra) + defaults['__containers'] = containers attrs = self.factory.attributes(create, defaults) @@ -146,8 +255,19 @@ class SubFactory(OrderedDeclaration): def lazy_attribute(func): return LazyAttribute(func) +def iterator(func): + """Turn a generator function into an iterator attribute.""" + return Iterator(func()) + +def infinite_iterator(func): + """Turn a generator function into an infinite iterator attribute.""" + return InfiniteIterator(func()) + def sequence(func): return Sequence(func) def lazy_attribute_sequence(func): return LazyAttributeSequence(func) + +def container_attribute(func): + return ContainerAttribute(func, strict=False) @@ -5,7 +5,7 @@ from distutils.core import setup from distutils import cmd # Remember to change in factory/__init__.py as well! -VERSION = '1.0.4' +VERSION = '1.1.2' class test(cmd.Command): @@ -38,9 +38,9 @@ class test(cmd.Command): setup( - name='factory_boy_rbarrois', + name='factory_boy', version=VERSION, - description="A test fixtures replacement based on thoughtbot's factory_girl for Ruby (originally by mark@deliciouslynerdy.com).", + description="A verstile test fixtures replacement based on thoughtbot's factory_girl for Ruby.", author='Mark Sandstrom', author_email='mark@deliciouslynerdy.com', maintainer='Raphaël Barrois', diff --git a/tests/__init__.py b/tests/__init__.py index 14f1e9d..7ab3567 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,3 +4,4 @@ from .test_base import * from .test_containers import * from .test_declarations import * +from .test_using import * diff --git a/tests/test_base.py b/tests/test_base.py index 68451f3..8da655e 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -21,8 +21,9 @@ # THE SOFTWARE. import unittest +import warnings -from factory.base import BaseFactory, Factory, StubFactory, BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY +from factory import base from factory import declarations class TestObject(object): @@ -52,174 +53,26 @@ class TestModel(FakeDjangoModel): class SafetyTestCase(unittest.TestCase): def testBaseFactory(self): - self.assertRaises(RuntimeError, BaseFactory) + self.assertRaises(RuntimeError, base.BaseFactory) class FactoryTestCase(unittest.TestCase): - def testAttribute(self): - class TestObjectFactory(Factory): - one = 'one' - - test_object = TestObjectFactory.build() - self.assertEqual(test_object.one, 'one') - - def testSequence(self): - class TestObjectFactory(Factory): - one = declarations.Sequence(lambda n: 'one' + n) - two = declarations.Sequence(lambda n: 'two' + n) - - test_object0 = TestObjectFactory.build() - self.assertEqual(test_object0.one, 'one0') - self.assertEqual(test_object0.two, 'two0') - - test_object1 = TestObjectFactory.build() - self.assertEqual(test_object1.one, 'one1') - self.assertEqual(test_object1.two, 'two1') - - def testSequenceCustomBegin(self): - class TestObjectFactory(Factory): - @classmethod - def _setup_next_sequence(cls): - return 42 - - one = declarations.Sequence(lambda n: 'one' + n) - two = declarations.Sequence(lambda n: 'two' + n) - - test_object0 = TestObjectFactory.build() - self.assertEqual('one42', test_object0.one) - self.assertEqual('two42', test_object0.two) - - test_object1 = TestObjectFactory.build() - self.assertEqual('one43', test_object1.one) - self.assertEqual('two43', test_object1.two) + def testDisplay(self): + class TestObjectFactory(base.Factory): + FACTORY_FOR = FakeDjangoModel - def testLazyAttribute(self): - class TestObjectFactory(Factory): - one = declarations.LazyAttribute(lambda a: 'abc' ) - two = declarations.LazyAttribute(lambda a: a.one + ' xyz') - - test_object = TestObjectFactory.build() - self.assertEqual(test_object.one, 'abc') - self.assertEqual(test_object.two, 'abc xyz') + self.assertIn('TestObjectFactory', str(TestObjectFactory)) + self.assertIn('FakeDjangoModel', str(TestObjectFactory)) def testLazyAttributeNonExistentParam(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = declarations.LazyAttribute(lambda a: a.does_not_exist ) self.assertRaises(AttributeError, TestObjectFactory) - def testLazyAttributeSequence(self): - class TestObjectFactory(Factory): - one = declarations.LazyAttributeSequence(lambda a, n: 'abc' + n) - two = declarations.LazyAttributeSequence(lambda a, n: a.one + ' xyz' + n) - - test_object0 = TestObjectFactory.build() - self.assertEqual(test_object0.one, 'abc0') - self.assertEqual(test_object0.two, 'abc0 xyz0') - - test_object1 = TestObjectFactory.build() - self.assertEqual(test_object1.one, 'abc1') - self.assertEqual(test_object1.two, 'abc1 xyz1') - - def testLazyAttributeDecorator(self): - class TestObjectFactory(Factory): - @declarations.lazy_attribute - def one(a): - return 'one' - - test_object = TestObjectFactory.build() - self.assertEqual(test_object.one, 'one') - - def testSelfAttribute(self): - class TestObjectFactory(Factory): - one = 'xx' - two = declarations.SelfAttribute('one') - - test_object = TestObjectFactory.build(one=1) - self.assertEqual(1, test_object.two) - - def testSequenceDecorator(self): - class TestObjectFactory(Factory): - @declarations.sequence - def one(n): - return 'one' + n - - test_object = TestObjectFactory.build() - self.assertEqual(test_object.one, 'one0') - - def testLazyAttributeSequenceDecorator(self): - class TestObjectFactory(Factory): - @declarations.lazy_attribute_sequence - def one(a, n): - return 'one' + n - @declarations.lazy_attribute_sequence - def two(a, n): - return a.one + ' two' + n - - test_object = TestObjectFactory.build() - self.assertEqual(test_object.one, 'one0') - self.assertEqual(test_object.two, 'one0 two0') - - def testBuildWithParameters(self): - class TestObjectFactory(Factory): - one = declarations.Sequence(lambda n: 'one' + n) - two = declarations.Sequence(lambda n: 'two' + n) - - test_object0 = TestObjectFactory.build(three='three') - self.assertEqual(test_object0.one, 'one0') - self.assertEqual(test_object0.two, 'two0') - self.assertEqual(test_object0.three, 'three') - - test_object1 = TestObjectFactory.build(one='other') - self.assertEqual(test_object1.one, 'other') - self.assertEqual(test_object1.two, 'two1') - - def testCreate(self): - class TestModelFactory(Factory): - one = 'one' - - test_model = TestModelFactory.create() - self.assertEqual(test_model.one, 'one') - self.assertTrue(test_model.id) - - def testInheritance(self): - class TestObjectFactory(Factory): - one = 'one' - two = declarations.LazyAttribute(lambda a: a.one + ' two') - - class TestObjectFactory2(TestObjectFactory): - FACTORY_FOR = TestObject - - three = 'three' - four = declarations.LazyAttribute(lambda a: a.three + ' four') - - test_object = TestObjectFactory2.build() - self.assertEqual(test_object.one, 'one') - self.assertEqual(test_object.two, 'one two') - self.assertEqual(test_object.three, 'three') - self.assertEqual(test_object.four, 'three four') - - test_object_alt = TestObjectFactory.build() - self.assertEqual(None, test_object_alt.three) - - def testInheritanceWithInheritedClass(self): - class TestObjectFactory(Factory): - one = 'one' - two = declarations.LazyAttribute(lambda a: a.one + ' two') - - class TestFactory(TestObjectFactory): - three = 'three' - four = declarations.LazyAttribute(lambda a: a.three + ' four') - - test_object = TestFactory.build() - self.assertEqual(test_object.one, 'one') - self.assertEqual(test_object.two, 'one two') - self.assertEqual(test_object.three, 'three') - self.assertEqual(test_object.four, 'three four') - def testInheritanceWithSequence(self): """Tests that sequence IDs are shared between parent and son.""" - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = declarations.Sequence(lambda a: a) class TestSubFactory(TestObjectFactory): @@ -232,47 +85,17 @@ class FactoryTestCase(unittest.TestCase): ones = set([x.one for x in (parent, alt_parent, sub, alt_sub)]) self.assertEqual(4, len(ones)) - def testDualInheritance(self): - class TestObjectFactory(Factory): - one = 'one' - - class TestOtherFactory(Factory): - FACTORY_FOR = TestObject - two = 'two' - four = 'four' - - class TestFactory(TestObjectFactory, TestOtherFactory): - three = 'three' - - obj = TestFactory.build(two=2) - self.assertEqual('one', obj.one) - self.assertEqual(2, obj.two) - self.assertEqual('three', obj.three) - self.assertEqual('four', obj.four) - - def testSetCreationFunction(self): - def creation_function(class_to_create, **kwargs): - return "This doesn't even return an instance of {0}".format(class_to_create.__name__) - - class TestModelFactory(Factory): - pass - - TestModelFactory.set_creation_function(creation_function) - - test_object = TestModelFactory.create() - self.assertEqual(test_object, "This doesn't even return an instance of TestModel") - class FactoryDefaultStrategyTestCase(unittest.TestCase): def setUp(self): - self.default_strategy = Factory.default_strategy + self.default_strategy = base.Factory.default_strategy def tearDown(self): - Factory.default_strategy = self.default_strategy + base.Factory.default_strategy = self.default_strategy def testBuildStrategy(self): - Factory.default_strategy = BUILD_STRATEGY + base.Factory.default_strategy = base.BUILD_STRATEGY - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): one = 'one' test_model = TestModelFactory() @@ -282,106 +105,17 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): def testCreateStrategy(self): # Default default_strategy - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): one = 'one' test_model = TestModelFactory() self.assertEqual(test_model.one, 'one') self.assertTrue(test_model.id) - def testSubFactory(self): - class TestModel2(FakeDjangoModel): - pass - - class TestModelFactory(Factory): - FACTORY_FOR = TestModel - one = 3 - - class TestModel2Factory(Factory): - FACTORY_FOR = TestModel2 - two = declarations.SubFactory(TestModelFactory, one=1) - - test_model = TestModel2Factory(two__one=4) - self.assertEqual(4, test_model.two.one) - self.assertEqual(1, test_model.id) - self.assertEqual(1, test_model.two.id) - - def testSubFactoryWithLazyFields(self): - class TestModel2(FakeDjangoModel): - pass - - class TestModelFactory(Factory): - FACTORY_FOR = TestModel - - class TestModel2Factory(Factory): - FACTORY_FOR = TestModel2 - two = declarations.SubFactory(TestModelFactory, - one=declarations.Sequence(lambda n: 'x%sx' % n), - two=declarations.LazyAttribute( - lambda o: '%s%s' % (o.one, o.one))) - - test_model = TestModel2Factory(one=42) - self.assertEqual('x0x', test_model.two.one) - self.assertEqual('x0xx0x', test_model.two.two) - - def testNestedSubFactory(self): - """Test nested sub-factories.""" - - class TestObject(object): - def __init__(self, **kwargs): - for k, v in kwargs.iteritems(): - setattr(self, k, v) - - class TestObjectFactory(Factory): - FACTORY_FOR = TestObject - - class WrappingTestObjectFactory(Factory): - FACTORY_FOR = TestObject - - wrapped = declarations.SubFactory(TestObjectFactory) - wrapped_bis = declarations.SubFactory(TestObjectFactory, one=1) - - class OuterWrappingTestObjectFactory(Factory): - FACTORY_FOR = TestObject - - wrap = declarations.SubFactory(WrappingTestObjectFactory, wrapped__two=2) - - outer = OuterWrappingTestObjectFactory.build() - self.assertEqual(outer.wrap.wrapped.two, 2) - self.assertEqual(outer.wrap.wrapped_bis.one, 1) - - def testNestedSubFactoryWithOverriddenSubFactories(self): - """Test nested sub-factories, with attributes overridden with subfactories.""" - - class TestObject(object): - def __init__(self, **kwargs): - for k, v in kwargs.iteritems(): - setattr(self, k, v) - - class TestObjectFactory(Factory): - FACTORY_FOR = TestObject - two = 'two' - - class WrappingTestObjectFactory(Factory): - FACTORY_FOR = TestObject - - wrapped = declarations.SubFactory(TestObjectFactory) - friend = declarations.LazyAttribute(lambda o: o.wrapped.two.four + 1) - - class OuterWrappingTestObjectFactory(Factory): - FACTORY_FOR = TestObject - - wrap = declarations.SubFactory(WrappingTestObjectFactory, - wrapped__two=declarations.SubFactory(TestObjectFactory, four=4)) - - outer = OuterWrappingTestObjectFactory.build() - self.assertEqual(outer.wrap.wrapped.two.four, 4) - self.assertEqual(outer.wrap.friend, 5) - def testStubStrategy(self): - Factory.default_strategy = STUB_STRATEGY + base.Factory.default_strategy = base.STUB_STRATEGY - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): one = 'one' test_model = TestModelFactory() @@ -389,54 +123,69 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): self.assertFalse(hasattr(test_model, 'id')) # We should have a plain old object def testUnknownStrategy(self): - Factory.default_strategy = 'unknown' + base.Factory.default_strategy = 'unknown' - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): one = 'one' - self.assertRaises(Factory.UnknownStrategy, TestModelFactory) + self.assertRaises(base.Factory.UnknownStrategy, TestModelFactory) def testStubWithNonStubStrategy(self): - class TestModelFactory(StubFactory): + class TestModelFactory(base.StubFactory): one = 'one' - TestModelFactory.default_strategy = CREATE_STRATEGY + TestModelFactory.default_strategy = base.CREATE_STRATEGY - self.assertRaises(StubFactory.UnsupportedStrategy, TestModelFactory) + self.assertRaises(base.StubFactory.UnsupportedStrategy, TestModelFactory) - TestModelFactory.default_strategy = BUILD_STRATEGY - self.assertRaises(StubFactory.UnsupportedStrategy, TestModelFactory) + TestModelFactory.default_strategy = base.BUILD_STRATEGY + self.assertRaises(base.StubFactory.UnsupportedStrategy, TestModelFactory) class FactoryCreationTestCase(unittest.TestCase): def testFactoryFor(self): - class TestFactory(Factory): + class TestFactory(base.Factory): FACTORY_FOR = TestObject self.assertTrue(isinstance(TestFactory.build(), TestObject)) def testAutomaticAssociatedClassDiscovery(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): pass self.assertTrue(isinstance(TestObjectFactory.build(), TestObject)) + def testDeprecationWarning(self): + """Make sure the 'auto-discovery' deprecation warning is issued.""" + + with warnings.catch_warnings(record=True) as w: + # Clear the warning registry. + if hasattr(base, '__warningregistry__'): + base.__warningregistry__.clear() + + warnings.simplefilter('always') + class TestObjectFactory(base.Factory): + pass + + self.assertEqual(1, len(w)) + self.assertIn('deprecated', str(w[0].message)) + def testStub(self): - class TestFactory(StubFactory): + class TestFactory(base.StubFactory): pass - self.assertEqual(TestFactory.default_strategy, STUB_STRATEGY) + self.assertEqual(TestFactory.default_strategy, base.STUB_STRATEGY) def testInheritanceWithStub(self): - class TestObjectFactory(StubFactory): + class TestObjectFactory(base.StubFactory): pass class TestFactory(TestObjectFactory): pass - self.assertEqual(TestFactory.default_strategy, STUB_STRATEGY) + self.assertEqual(TestFactory.default_strategy, base.STUB_STRATEGY) def testCustomCreation(self): - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): @classmethod def _prepare(cls, create, **kwargs): kwargs['four'] = 4 @@ -456,18 +205,18 @@ class FactoryCreationTestCase(unittest.TestCase): def testNoAssociatedClassWithAutodiscovery(self): try: - class TestFactory(Factory): + class TestFactory(base.Factory): pass self.fail() - except Factory.AssociatedClassError as e: + except base.Factory.AssociatedClassError as e: self.assertTrue('autodiscovery' in str(e)) def testNoAssociatedClassWithoutAutodiscovery(self): try: - class Test(Factory): + class Test(base.Factory): pass self.fail() - except Factory.AssociatedClassError as e: + except base.Factory.AssociatedClassError as e: self.assertTrue('autodiscovery' not in str(e)) diff --git a/tests/test_containers.py b/tests/test_containers.py index 34b61d4..6e58573 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -43,18 +43,52 @@ class LazyStubTestCase(unittest.TestCase): self.assertRaises(AttributeError, getattr, stub, 'three') + def test_accessing_container(self): + class LazyAttr(containers.LazyValue): + def __init__(self, obj_attr, container_attr): + self.obj_attr = obj_attr + self.container_attr = container_attr + + def evaluate(self, obj, containers=()): + if containers: + add = getattr(containers[0], self.container_attr) + else: + add = 0 + return getattr(obj, self.obj_attr) + add + + class DummyContainer(object): + three = 3 + + stub = containers.LazyStub({'one': LazyAttr('two', 'three'), 'two': 2, 'three': 42}, + containers=(DummyContainer(),)) + + self.assertEqual(5, stub.one) + + stub = containers.LazyStub({'one': LazyAttr('two', 'three'), 'two': 2, 'three': 42}, + containers=()) + self.assertEqual(2, stub.one) + def test_cyclic_definition(self): class LazyAttr(containers.LazyValue): def __init__(self, attrname): self.attrname = attrname - def evaluate(self, obj): + def evaluate(self, obj, container=None): return 1 + getattr(obj, self.attrname) stub = containers.LazyStub({'one': LazyAttr('two'), 'two': LazyAttr('one')}) self.assertRaises(containers.CyclicDefinitionError, getattr, stub, 'one') + def test_representation(self): + class RandomObj(object): + pass + + stub = containers.LazyStub({'one': 1, 'two': 2}, target_class=RandomObj) + self.assertIn('RandomObj', repr(stub)) + self.assertIn('RandomObj', str(stub)) + self.assertIn('one', str(stub)) + class OrderedDeclarationMock(declarations.OrderedDeclaration): pass @@ -134,7 +168,13 @@ class DeclarationDictTestCase(unittest.TestCase): def test_update_with_public(self): d = containers.DeclarationDict() - d.update_with_public({'one': 1, '_two': 2, 'three': 3}) + d.update_with_public({ + 'one': 1, + '_two': 2, + 'three': 3, + 'classmethod': classmethod(lambda c: 1), + 'staticmethod': staticmethod(lambda: 1), + }) self.assertEqual(set(['one', 'three']), set(d)) self.assertEqual(set([1, 3]), set(d.values())) diff --git a/tests/test_declarations.py b/tests/test_declarations.py index dcee38b..7215a54 100644 --- a/tests/test_declarations.py +++ b/tests/test_declarations.py @@ -22,12 +22,38 @@ import unittest -from factory.declarations import OrderedDeclaration, Sequence +from factory.declarations import deepgetattr, OrderedDeclaration, Sequence class OrderedDeclarationTestCase(unittest.TestCase): def test_errors(self): decl = OrderedDeclaration() self.assertRaises(NotImplementedError, decl.evaluate, None, {}) + +class DigTestCase(unittest.TestCase): + class MyObj(object): + def __init__(self, n): + self.n = n + + def test_chaining(self): + obj = self.MyObj(1) + obj.a = self.MyObj(2) + obj.a.b = self.MyObj(3) + obj.a.b.c = self.MyObj(4) + + self.assertEqual(2, deepgetattr(obj, 'a').n) + self.assertRaises(AttributeError, deepgetattr, obj, 'b') + self.assertEqual(2, deepgetattr(obj, 'a.n')) + self.assertEqual(3, deepgetattr(obj, 'a.c', 3)) + self.assertRaises(AttributeError, deepgetattr, obj, 'a.c.n') + self.assertRaises(AttributeError, deepgetattr, obj, 'a.d') + self.assertEqual(3, deepgetattr(obj, 'a.b').n) + self.assertEqual(3, deepgetattr(obj, 'a.b.n')) + self.assertEqual(4, deepgetattr(obj, 'a.b.c').n) + self.assertEqual(4, deepgetattr(obj, 'a.b.c.n')) + self.assertEqual(42, deepgetattr(obj, 'a.b.c.n.x', 42)) + + + if __name__ == '__main__': unittest.main() diff --git a/tests/test_using.py b/tests/test_using.py new file mode 100644 index 0000000..41b34ea --- /dev/null +++ b/tests/test_using.py @@ -0,0 +1,810 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2011 Raphaël Barrois +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""Tests using factory.""" + +import unittest + +import factory + + + +class TestObject(object): + def __init__(self, one=None, two=None, three=None, four=None, five=None): + self.one = one + self.two = two + self.three = three + self.four = four + self.five = five + +class FakeDjangoModel(object): + class FakeDjangoManager(object): + def create(self, **kwargs): + fake_model = FakeDjangoModel(**kwargs) + fake_model.id = 1 + return fake_model + + objects = FakeDjangoManager() + + def __init__(self, **kwargs): + for name, value in kwargs.iteritems(): + setattr(self, name, value) + self.id = None + +class TestModel(FakeDjangoModel): + pass + + +class SimpleBuildTestCase(unittest.TestCase): + """Tests the minimalist 'factory.build/create' functions.""" + + def test_build(self): + obj = factory.build(TestObject, two=2) + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, None) + self.assertEqual(obj.four, None) + + def test_complex(self): + obj = factory.build(TestObject, two=2, three=factory.LazyAttribute(lambda o: o.two + 1)) + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, 3) + self.assertEqual(obj.four, None) + + def test_build_batch(self): + objs = factory.build_batch(TestObject, 4, two=2, + three=factory.LazyAttribute(lambda o: o.two + 1)) + + self.assertEqual(4, len(objs)) + self.assertEqual(4, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, 3) + self.assertEqual(obj.four, None) + + def test_create(self): + obj = factory.create(FakeDjangoModel, foo='bar') + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_create_batch(self): + objs = factory.create_batch(FakeDjangoModel, 4, foo='bar') + + self.assertEqual(4, len(objs)) + self.assertEqual(4, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_stub(self): + obj = factory.stub(TestObject, three=3) + self.assertEqual(obj.three, 3) + self.assertFalse(hasattr(obj, 'two')) + + def test_stub_batch(self): + objs = factory.stub_batch(FakeDjangoModel, 4, foo='bar') + + self.assertEqual(4, len(objs)) + self.assertEqual(4, len(set(objs))) + + for obj in objs: + self.assertFalse(hasattr(obj, 'id')) + self.assertEqual(obj.foo, 'bar') + + def test_generate_build(self): + obj = factory.generate(FakeDjangoModel, factory.BUILD_STRATEGY, foo='bar') + self.assertEqual(obj.id, None) + self.assertEqual(obj.foo, 'bar') + + def test_generate_create(self): + obj = factory.generate(FakeDjangoModel, factory.CREATE_STRATEGY, foo='bar') + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_generate_stub(self): + obj = factory.generate(FakeDjangoModel, factory.STUB_STRATEGY, foo='bar') + self.assertFalse(hasattr(obj, 'id')) + self.assertEqual(obj.foo, 'bar') + + def test_generate_batch_build(self): + objs = factory.generate_batch(FakeDjangoModel, factory.BUILD_STRATEGY, 20, foo='bar') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.id, None) + self.assertEqual(obj.foo, 'bar') + + def test_generate_batch_create(self): + objs = factory.generate_batch(FakeDjangoModel, factory.CREATE_STRATEGY, 20, foo='bar') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_generate_batch_stub(self): + objs = factory.generate_batch(FakeDjangoModel, factory.STUB_STRATEGY, 20, foo='bar') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for obj in objs: + self.assertFalse(hasattr(obj, 'id')) + self.assertEqual(obj.foo, 'bar') + + def test_simple_generate_build(self): + obj = factory.simple_generate(FakeDjangoModel, False, foo='bar') + self.assertEqual(obj.id, None) + self.assertEqual(obj.foo, 'bar') + + def test_simple_generate_create(self): + obj = factory.simple_generate(FakeDjangoModel, True, foo='bar') + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_simple_generate_batch_build(self): + objs = factory.simple_generate_batch(FakeDjangoModel, False, 20, foo='bar') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.id, None) + self.assertEqual(obj.foo, 'bar') + + def test_simple_generate_batch_create(self): + objs = factory.simple_generate_batch(FakeDjangoModel, True, 20, foo='bar') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_make_factory(self): + fact = factory.make_factory(TestObject, two=2, three=factory.LazyAttribute(lambda o: o.two + 1)) + + obj = fact.build() + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, 3) + self.assertEqual(obj.four, None) + + obj = fact.build(two=4) + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 4) + self.assertEqual(obj.three, 5) + self.assertEqual(obj.four, None) + + +class FactoryTestCase(unittest.TestCase): + def testAttribute(self): + class TestObjectFactory(factory.Factory): + one = 'one' + + test_object = TestObjectFactory.build() + self.assertEqual(test_object.one, 'one') + + def testSequence(self): + class TestObjectFactory(factory.Factory): + one = factory.Sequence(lambda n: 'one' + n) + two = factory.Sequence(lambda n: 'two' + n) + + test_object0 = TestObjectFactory.build() + self.assertEqual(test_object0.one, 'one0') + self.assertEqual(test_object0.two, 'two0') + + test_object1 = TestObjectFactory.build() + self.assertEqual(test_object1.one, 'one1') + self.assertEqual(test_object1.two, 'two1') + + def testSequenceCustomBegin(self): + class TestObjectFactory(factory.Factory): + @classmethod + def _setup_next_sequence(cls): + return 42 + + one = factory.Sequence(lambda n: 'one' + n) + two = factory.Sequence(lambda n: 'two' + n) + + test_object0 = TestObjectFactory.build() + self.assertEqual('one42', test_object0.one) + self.assertEqual('two42', test_object0.two) + + test_object1 = TestObjectFactory.build() + self.assertEqual('one43', test_object1.one) + self.assertEqual('two43', test_object1.two) + + def test_sequence_batch(self): + class TestObjectFactory(factory.Factory): + one = factory.Sequence(lambda n: 'one' + n) + two = factory.Sequence(lambda n: 'two' + n) + + objs = TestObjectFactory.build_batch(20) + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + for i, obj in enumerate(objs): + self.assertEqual('one%d' % i, obj.one) + self.assertEqual('two%d' % i, obj.two) + + def testLazyAttribute(self): + class TestObjectFactory(factory.Factory): + one = factory.LazyAttribute(lambda a: 'abc' ) + two = factory.LazyAttribute(lambda a: a.one + ' xyz') + + test_object = TestObjectFactory.build() + self.assertEqual(test_object.one, 'abc') + self.assertEqual(test_object.two, 'abc xyz') + + def testLazyAttributeSequence(self): + class TestObjectFactory(factory.Factory): + one = factory.LazyAttributeSequence(lambda a, n: 'abc' + n) + two = factory.LazyAttributeSequence(lambda a, n: a.one + ' xyz' + n) + + test_object0 = TestObjectFactory.build() + self.assertEqual(test_object0.one, 'abc0') + self.assertEqual(test_object0.two, 'abc0 xyz0') + + test_object1 = TestObjectFactory.build() + self.assertEqual(test_object1.one, 'abc1') + self.assertEqual(test_object1.two, 'abc1 xyz1') + + def testLazyAttributeDecorator(self): + class TestObjectFactory(factory.Factory): + @factory.lazy_attribute + def one(a): + return 'one' + + test_object = TestObjectFactory.build() + self.assertEqual(test_object.one, 'one') + + def testSelfAttribute(self): + class TmpObj(object): + n = 3 + + class TestObjectFactory(factory.Factory): + one = 'xx' + two = factory.SelfAttribute('one') + three = TmpObj() + four = factory.SelfAttribute('three.n') + five = factory.SelfAttribute('three.nnn', 5) + + test_object = TestObjectFactory.build(one=1) + self.assertEqual(1, test_object.two) + self.assertEqual(3, test_object.three.n) + self.assertEqual(3, test_object.four) + self.assertEqual(5, test_object.five) + + def testSequenceDecorator(self): + class TestObjectFactory(factory.Factory): + @factory.sequence + def one(n): + return 'one' + n + + test_object = TestObjectFactory.build() + self.assertEqual(test_object.one, 'one0') + + def testLazyAttributeSequenceDecorator(self): + class TestObjectFactory(factory.Factory): + @factory.lazy_attribute_sequence + def one(a, n): + return 'one' + n + @factory.lazy_attribute_sequence + def two(a, n): + return a.one + ' two' + n + + test_object = TestObjectFactory.build() + self.assertEqual(test_object.one, 'one0') + self.assertEqual(test_object.two, 'one0 two0') + + def testBuildWithParameters(self): + class TestObjectFactory(factory.Factory): + one = factory.Sequence(lambda n: 'one' + n) + two = factory.Sequence(lambda n: 'two' + n) + + test_object0 = TestObjectFactory.build(three='three') + self.assertEqual(test_object0.one, 'one0') + self.assertEqual(test_object0.two, 'two0') + self.assertEqual(test_object0.three, 'three') + + test_object1 = TestObjectFactory.build(one='other') + self.assertEqual(test_object1.one, 'other') + self.assertEqual(test_object1.two, 'two1') + + def testCreate(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.create() + self.assertEqual(test_model.one, 'one') + self.assertTrue(test_model.id) + + def test_create_batch(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.create_batch(20, two=factory.Sequence(int)) + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual(i, obj.two) + self.assertTrue(obj.id) + + def test_generate_build(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.generate(factory.BUILD_STRATEGY) + self.assertEqual(test_model.one, 'one') + self.assertFalse(test_model.id) + + def test_generate_create(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.generate(factory.CREATE_STRATEGY) + self.assertEqual(test_model.one, 'one') + self.assertTrue(test_model.id) + + def test_generate_stub(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.generate(factory.STUB_STRATEGY) + self.assertEqual(test_model.one, 'one') + self.assertFalse(hasattr(test_model, 'id')) + + def test_generate_batch_build(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.generate_batch(factory.BUILD_STRATEGY, 20, two='two') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual('two', obj.two) + self.assertFalse(obj.id) + + def test_generate_batch_create(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.generate_batch(factory.CREATE_STRATEGY, 20, two='two') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual('two', obj.two) + self.assertTrue(obj.id) + + def test_generate_batch_stub(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.generate_batch(factory.STUB_STRATEGY, 20, two='two') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual('two', obj.two) + self.assertFalse(hasattr(obj, 'id')) + + def test_simple_generate_build(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.simple_generate(False) + self.assertEqual(test_model.one, 'one') + self.assertFalse(test_model.id) + + def test_simple_generate_create(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.simple_generate(True) + self.assertEqual(test_model.one, 'one') + self.assertTrue(test_model.id) + + def test_simple_generate_batch_build(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.simple_generate_batch(False, 20, two='two') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual('two', obj.two) + self.assertFalse(obj.id) + + def test_simple_generate_batch_create(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.simple_generate_batch(True, 20, two='two') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual('two', obj.two) + self.assertTrue(obj.id) + + def test_stub_batch(self): + class TestObjectFactory(factory.Factory): + one = 'one' + two = factory.LazyAttribute(lambda a: a.one + ' two') + three = factory.Sequence(lambda n: int(n)) + + objs = TestObjectFactory.stub_batch(20, + one=factory.Sequence(lambda n: n)) + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual(str(i), obj.one) + self.assertEqual('%d two' % i, obj.two) + self.assertEqual(i, obj.three) + + def testInheritance(self): + class TestObjectFactory(factory.Factory): + one = 'one' + two = factory.LazyAttribute(lambda a: a.one + ' two') + + class TestObjectFactory2(TestObjectFactory): + FACTORY_FOR = TestObject + + three = 'three' + four = factory.LazyAttribute(lambda a: a.three + ' four') + + test_object = TestObjectFactory2.build() + self.assertEqual(test_object.one, 'one') + self.assertEqual(test_object.two, 'one two') + self.assertEqual(test_object.three, 'three') + self.assertEqual(test_object.four, 'three four') + + test_object_alt = TestObjectFactory.build() + self.assertEqual(None, test_object_alt.three) + + def testInheritanceWithInheritedClass(self): + class TestObjectFactory(factory.Factory): + one = 'one' + two = factory.LazyAttribute(lambda a: a.one + ' two') + + class TestFactory(TestObjectFactory): + three = 'three' + four = factory.LazyAttribute(lambda a: a.three + ' four') + + test_object = TestFactory.build() + self.assertEqual(test_object.one, 'one') + self.assertEqual(test_object.two, 'one two') + self.assertEqual(test_object.three, 'three') + self.assertEqual(test_object.four, 'three four') + + def testDualInheritance(self): + class TestObjectFactory(factory.Factory): + one = 'one' + + class TestOtherFactory(factory.Factory): + FACTORY_FOR = TestObject + two = 'two' + four = 'four' + + class TestFactory(TestObjectFactory, TestOtherFactory): + three = 'three' + + obj = TestFactory.build(two=2) + self.assertEqual('one', obj.one) + self.assertEqual(2, obj.two) + self.assertEqual('three', obj.three) + self.assertEqual('four', obj.four) + + def testSetCreationFunction(self): + def creation_function(class_to_create, **kwargs): + return "This doesn't even return an instance of {0}".format(class_to_create.__name__) + + class TestModelFactory(factory.Factory): + pass + + TestModelFactory.set_creation_function(creation_function) + + test_object = TestModelFactory.create() + self.assertEqual(test_object, "This doesn't even return an instance of TestModel") + + def testClassMethodAccessible(self): + class TestObjectFactory(factory.Factory): + @classmethod + def alt_create(cls, **kwargs): + return kwargs + + self.assertEqual(TestObjectFactory.alt_create(foo=1), {"foo": 1}) + + def testStaticMethodAccessible(self): + class TestObjectFactory(factory.Factory): + @staticmethod + def alt_create(**kwargs): + return kwargs + + self.assertEqual(TestObjectFactory.alt_create(foo=1), {"foo": 1}) + + +class SubFactoryTestCase(unittest.TestCase): + def testSubFactory(self): + class TestModel2(FakeDjangoModel): + pass + + class TestModelFactory(factory.Factory): + FACTORY_FOR = TestModel + one = 3 + + class TestModel2Factory(factory.Factory): + FACTORY_FOR = TestModel2 + two = factory.SubFactory(TestModelFactory, one=1) + + test_model = TestModel2Factory(two__one=4) + self.assertEqual(4, test_model.two.one) + self.assertEqual(1, test_model.id) + self.assertEqual(1, test_model.two.id) + + def testSubFactoryWithLazyFields(self): + class TestModel2(FakeDjangoModel): + pass + + class TestModelFactory(factory.Factory): + FACTORY_FOR = TestModel + + class TestModel2Factory(factory.Factory): + FACTORY_FOR = TestModel2 + two = factory.SubFactory(TestModelFactory, + one=factory.Sequence(lambda n: 'x%sx' % n), + two=factory.LazyAttribute( + lambda o: '%s%s' % (o.one, o.one))) + + test_model = TestModel2Factory(one=42) + self.assertEqual('x0x', test_model.two.one) + self.assertEqual('x0xx0x', test_model.two.two) + + def testSubFactoryOverriding(self): + class TestObject(object): + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + class WrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrapped = factory.SubFactory(TestObjectFactory, two=2, four=4) + wrapped__two = 4 + wrapped__three = 3 + + wrapping = WrappingTestObjectFactory.build() + self.assertEqual(wrapping.wrapped.two, 4) + self.assertEqual(wrapping.wrapped.three, 3) + self.assertEqual(wrapping.wrapped.four, 4) + + def testNestedSubFactory(self): + """Test nested sub-factories.""" + + class TestObject(object): + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + class WrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrapped = factory.SubFactory(TestObjectFactory) + wrapped_bis = factory.SubFactory(TestObjectFactory, one=1) + + class OuterWrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrap = factory.SubFactory(WrappingTestObjectFactory, wrapped__two=2) + + outer = OuterWrappingTestObjectFactory.build() + self.assertEqual(outer.wrap.wrapped.two, 2) + self.assertEqual(outer.wrap.wrapped_bis.one, 1) + + def testNestedSubFactoryWithOverriddenSubFactories(self): + """Test nested sub-factories, with attributes overridden with subfactories.""" + + class TestObject(object): + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + two = 'two' + + class WrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrapped = factory.SubFactory(TestObjectFactory) + friend = factory.LazyAttribute(lambda o: o.wrapped.two.four + 1) + + class OuterWrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrap = factory.SubFactory(WrappingTestObjectFactory, + wrapped__two=factory.SubFactory(TestObjectFactory, four=4)) + + outer = OuterWrappingTestObjectFactory.build() + self.assertEqual(outer.wrap.wrapped.two.four, 4) + self.assertEqual(outer.wrap.friend, 5) + + def testSubFactoryAndInheritance(self): + """Test inheriting from a factory with subfactories, overriding.""" + class TestObject(object): + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + two = 'two' + + class WrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrapped = factory.SubFactory(TestObjectFactory) + friend = factory.LazyAttribute(lambda o: o.wrapped.two + 1) + + class ExtendedWrappingTestObjectFactory(WrappingTestObjectFactory): + wrapped__two = 4 + + wrapping = ExtendedWrappingTestObjectFactory.build() + self.assertEqual(wrapping.wrapped.two, 4) + self.assertEqual(wrapping.friend, 5) + + def testDiamondSubFactory(self): + """Tests the case where an object has two fields with a common field.""" + class InnerMost(object): + def __init__(self, a, b): + self.a = a + self.b = b + + class SideA(object): + def __init__(self, inner_from_a): + self.inner_from_a = inner_from_a + + class SideB(object): + def __init__(self, inner_from_b): + self.inner_from_b = inner_from_b + + class OuterMost(object): + def __init__(self, foo, side_a, side_b): + self.foo = foo + self.side_a = side_a + self.side_b = side_b + + class InnerMostFactory(factory.Factory): + FACTORY_FOR = InnerMost + a = 15 + b = 20 + + class SideAFactory(factory.Factory): + FACTORY_FOR = SideA + inner_from_a = factory.SubFactory(InnerMostFactory, a=20) + + class SideBFactory(factory.Factory): + FACTORY_FOR = SideB + inner_from_b = factory.SubFactory(InnerMostFactory, b=15) + + class OuterMostFactory(factory.Factory): + FACTORY_FOR = OuterMost + + foo = 30 + side_a = factory.SubFactory(SideAFactory, + inner_from_a__a=factory.ContainerAttribute(lambda obj, containers: containers[1].foo * 2)) + side_b = factory.SubFactory(SideBFactory, + inner_from_b=factory.ContainerAttribute(lambda obj, containers: containers[0].side_a.inner_from_a)) + + outer = OuterMostFactory.build() + self.assertEqual(outer.foo, 30) + self.assertEqual(outer.side_a.inner_from_a, outer.side_b.inner_from_b) + self.assertEqual(outer.side_a.inner_from_a.a, outer.foo * 2) + self.assertEqual(outer.side_a.inner_from_a.b, 20) + + outer = OuterMostFactory.build(side_a__inner_from_a__b = 4) + self.assertEqual(outer.foo, 30) + self.assertEqual(outer.side_a.inner_from_a, outer.side_b.inner_from_b) + self.assertEqual(outer.side_a.inner_from_a.a, outer.foo * 2) + self.assertEqual(outer.side_a.inner_from_a.b, 4) + + +class IteratorTestCase(unittest.TestCase): + + def test_iterator(self): + class TestObjectFactory(factory.Factory): + one = factory.Iterator(xrange(10, 30)) + + objs = TestObjectFactory.build_batch(20) + + for i, obj in enumerate(objs): + self.assertEqual(i + 10, obj.one) + + def test_infinite_iterator(self): + class TestObjectFactory(factory.Factory): + one = factory.InfiniteIterator(xrange(5)) + + objs = TestObjectFactory.build_batch(20) + + for i, obj in enumerate(objs): + self.assertEqual(i % 5, obj.one) + + def test_iterator_decorator(self): + class TestObjectFactory(factory.Factory): + @factory.iterator + def one(): + for i in xrange(10, 50): + yield i + + objs = TestObjectFactory.build_batch(20) + + for i, obj in enumerate(objs): + self.assertEqual(i + 10, obj.one) + + def test_infinite_iterator_decorator(self): + class TestObjectFactory(factory.Factory): + @factory.infinite_iterator + def one(): + for i in xrange(5): + yield i + + objs = TestObjectFactory.build_batch(20) + + for i, obj in enumerate(objs): + self.assertEqual(i % 5, obj.one) + + + +if __name__ == '__main__': + unittest.main() |