FactoryGirl "create_list" not saving to database - ruby-on-rails

I'm seeing some weird behaviour in FactoryGirl that seems to contradict the documentation. In an Rspec test, if I do this;
static_groups = FactoryGirl.create_list(:static_group,5)
expect(StaticGroup.count).to eq(5)
The test fails (expected: 5, got: 0). If I add in explicit saves of the records;
static_groups = FactoryGirl.create_list(:static_group,5)
static_groups.each do |grp|
grp.save
end
expect(StaticGroup.count).to eq(5)
The test passes! I thought "create" in factorygirl was supposed to do a save for you, but in this case it is plainly not saving records which can be saved straight away afterwards! Even weirder, if I interrogate the objects in the first example (where I don't do an explicit save) they all have ids, so they've definately been talking to the database!!
EDIT
Before anyone points out the dodgy "count" syntax, I'm using DataMapper as an ORM, that's a valid way of counting :)

Ugh - nightmare.
Eventually figured out what it was - the "StaticGroup" class uses STI to figure its type out. This is a little more complex because it hooks into two databases and the class structure changed between legacy and new-world.
Long story short, I copy/pasted my group factory into the static group factory and forgot to change the type. There was a hook in the "save" method that sorts this out for me and it wasn't getting fired when factorygirl created the object.

Related

Maintianing ActiveRecord associations created using FactoryBot in controller test

I'm attempting to speed up some tests for a Rails controller and a bottleneck has to do with large numbers of objects being created and persisted to the database. I'm attempting to replace most of those create calls with build calls to address this.
Running Rails 5.1, and using MiniTest 5.10.3, with FactoryBot 5.0.2.
I'm attempting to go from this
#user = create(:user)
#item1 = create(:item)
#item1 = create(:item)
#transaction1 = create(:transaction, buyer: #buyer, item: #item1)
#transaction2 = create(:transaction, buyer: #buyer, item: #item2)
In this application item represents a sellable object, user represents a purchaser and transaction is the object that creates the two. The User class also has an association added to it transaction_checkout_items which returns all Transaction items which are in a state where the purchase can be completed.
So, with each test we're creating a myriad of objects and saving them all to the database. It's slow but it works. Still, I want it to be faster, so I've tried replacing the existing setup with something like this:
#user = create(:user)
def build_transaction_checkout_items(user, item)
user.transaction_checkout_items.build(attributes_for(:transaction,
buyer: user,
sale_price: item.sale_price,
item: item))
end
#item1 = build_stubbed(:item)
#item2 = build_stubbed(:item)
#transaction1 = build_transaction_checkout_items(#buyer, #item1)
#transaction2 = build_transaction_checkout_items(#buyer, #item2)
This seems to work as long as I'm in the test. If I drop a binding in my test and check the objects #user returns the user object, #user.transaction_checkout_items returns a Transaction::ActiveRecord_Associations_CollectionProxy object containing all my associated transactions, and the individual transactions have their associated items attached. However, If I put a binding.pry into the controller method which actually does the work, and look at the User object I see the correct one, but user.transaction_checkout_items now returns an empty Transaction::ActiveRecord_Associations_CollectionProxy object with nothing in it. Essentially the associations vanish, and this makes sense to me as the controller is pulling the User object from the database and going to work on it, and this new object is missing the associations. I've considered trying to stub out an any_instance method on the User class so that whenever #transaction_checkout_items is called it returns the collection of Transaction objects but I don't see any way to create a new ::ActiveRecord_Associations_CollectionProxy object. I can't simply use an array or other collection for this as there are methods on the ::ActiveRecord_Associations_CollectionProxy that need to be called for the controller logic to work.
So here I am on a Friday blocked. Is my idea of stubbing transaction_checkout_items on any User instance a good one, and if so how do I do it? Or is there an alternate strategy anyone can suggest that will allow the MiniTest stubbed associations to persist and be available when the controller code runs?
Is my idea of stubbing transaction_checkout_items on any User instance a good one, and if so how do I do it?
Stubbing out ActiveRecord methods is almost always a bad idea. It will couple your tests heavily to the implementation and potentially will make it difficult to update Rails / ActiveRecord as if anything changes in the framework your tests start breaking. There might be also lots of funny side effects you haven't thought about.
The question is also what do you actually want to test? If you start stubbing out these methods, what are you testing? In a controller / integration test, I would expect you want to test to fetch the correct records from the database.
Using build vs. create to improve test performance is a good trick but, as you already discovered, is only valuable if you use the same objects in the test. This is unfortunately not possible for integration tests and you need / should persists the records.
Or is there an alternate strategy anyone can suggest that will allow the MiniTest stubbed associations to persist and be available when the controller code runs?
I would think about why this is slow and if this is actually really a problem. How long does your test & whole test suite run or is this just a premature optimisation?
If the reason it's slow is solely because you need to create a lot of test data you could use fixtures or seed your database instead. This would both be faster than using FactoryBot although brings different issues (e.g. MysteryGuest)

Automatically Testing Models in Rails

I just, manually, discovered a migration error. I added a new field to a model, and forgot to add it into the model_params method of the controller. As a result the new field wasn't persisted to the database.
Easy enough to fix once I noticed the problem, but it got me to wondering if there was a way to detect this in testing. I would imagine something like a gem that would parse the schema and generate a set of tests to ensure that all of the fields could be written and that the same data could be read back.
Is this something that can be (or is) done? So far, my searches have led me to lots of interesting reading, but not to a gem like this...
It is possible to write what you would want. Iterate through all the fields in the model, generate params that mirrors those fields, and then run functional tests on your controllers. The problem is that the test is brittle. What if you don't actually want all the fields to be writable through params? What if you reference a model in another controller outside of the standard pattern? How will you handle generating data that would pass different validations? You would either have to be sure that your application would only be written in a certain way or this test would become more and more complex to handle additional edge cases.
I think the solution in testing would be to try to keep things simple; realize that you've made a change to the system and as a result of that change, corresponding tests would need to be updated. In this case, you would update the functional and unit tests affected by that model. If you were strictly adhering to Test Driven Design, you would actually update the tests first to produce a failing test and then implement the change. As a result, hopefully the updated functional test would have failed in this case.
Outside of testing, you may want to look into a linter. In essence, you're asking if you can catch an error where the parameters passed to an object's method doesn't match the signature. This is more catchable when parsing the code completely (i.e. compilation in a static type environment).
EDIT - I skipped a step on the linting, as you would also have to write your code a certain way that a linter would catch it, such as being more explicit of the method and parameters passed to it.
You might want to consider that such a gem may not exist because its not that practical or useful in real life.
Getting the columns off a model is pretty simple from the reflection methods that Active Record gives you. And yeah you could use that theoretically to automagically run a bunch of tests in loop.
But in reality its just not going to cut it. In real life you don't want every column to be assignable. Thats why you are using mass assignment protection in the first place.
And add to that the complexity of the different kinds of constraints and data types your models have. You'll end up with something extremely complex with just adds a bunch of tests with limited value.
If you find yourself omitting a property from mass assignment protection than your should try to cover that part of your controller either with a functional test or an integration test.
class ArticlesControllerTest < ActionController::TestCase
def valid_attributes
{
title: 'How to test like a Rockstar',
content: 'Bla bla bla'
}
end
test "created article should have the correct attributes" do
post :create, article: valid_attributes
article = Article.last
valid_attributes.keys.each do |key|
assert_equals article[key], valid_attributes[key]
end
end
end

How to test a Rails Model Class method which calls a method on all members?

In my Rails 4 project I've got a model, EventGenerator with an instance method generate (which creates some records in the database), and a class method generate_all which is intended to be called by a Rake task and looks something like this:
def self.generate_all
all.each(&:generate)
end
I can think of several approaches to testing this (I'm using RSpec 3 and Fabrication):
Don't bother - it's too simple to require tests. DHH says "Don't aim for 100% coverage". On the other hand, this is going to be called by a rake task, so won't be regularly exercised: I feel like that's a good reason to have tests.
Create a couple of EventGenerator instances in the database and use any_instance.should_receive(:generate) as the assertion - but RSpec 3 now recommends against this and requires a fudge to make it work. This is a personal 'showcase project' so if possible I'd like everything to be best-practice. Also (DHH aside) shouldn't it still be possible to create fast model specs which don't touch the database?
Like 2, but stub out EventGenerator.all to return some instances without touching the database. But stubbing the class under test is bad, right? And fragile.
Don't worry about unit testing it and instead cover it with an integration test: create a couple of generators in the database, run the method, check what gets changed/created in the database as a result.
Change the implementation: pass in an array of instances. But that's just pushing the problem back by a layer, and a test-driven change which I can't see benefitting the design.
Since I can't really think of a downside for option 4, perhaps that's the answer, but I feel like I need some science to back that up.
I would actually not bother to test it (so your 1.) as the method is really trivial.
If you would like to have it under test coverage though I'd suggest you to use your 3. My reasons are as follows:
Your test for .generate_all just needs to assert that the method #generate gets call on every instance returned by .all. It this context the actual implementation of .all is irrelevant and can be stubbed.
Your tests for #generate should assert that the method does the right thing. If these tests assert the proper functioning of this method, there's no need for the tests for .generate_all to duplicate any assertion.
Testing the proper functioning of #generate in the tests for .generate_all leads to unnecessary dependencies between the tests.

Alternatives to factory_girl

In my opinion and for my purposes, factory_girl completely sucks. Some limitations include:
No debugging support
If I include debugger statements, they are treated as model attributes. Instead of invoking the debugger, I just get strange errors.
FactoryGirl.define do
factory :admin do
name do
debugger # <-- Does not work.
Forgery(:name).full_name
end
email { Forgery(:email).address }
debugger # <-- Does not work either.
password "secret"
end
end
Limitations to associations
Am I too stupid or is there no elegant way to add two posts to a user?
FactoryGirl.define do
factory :post do
title "Foobar"
content "Some content"
end
factory :user do
name { Forgery(:name).full_name }
email { Forgery(:email).address }
# This does not work, if the Post model requires Post#user to be set.
posts [FactoryGirl.create(:post), FactoryGirl.create(:post)]
end
end
See also Factory Girl - Why are Records being continually created?
Tends to trigger strange bugs in rails
I can't remeber what happend, but often strange problems arise with factory_girl.
So given these examples. Are there any alternatives to factory_girl that do not have these issues?
I agree and found Factory Girl overly complicated for what it does.
I wrote a simpler gem a while ago which (at the time at least) was a drop in replacement for Factory Girl-based tests.
The factory definitions use much simpler Ruby and therefore behave as you would expect them to.
Check it out:
https://github.com/ginty/cranky
Why are you debugging inside the factory definition instead of in your code on the resulting objects?
And what's wrong with
user = FactoryGirl.create(:user)
2.times do
FactoryGirl.create(:post, user: user)
end
A debugger statement in a DSL can be problematic. You don't know when it will run.
FactoryGirl could run the DSL, save a representation of the factory, and use the internal representation in memory when the factory is used.
Also, there is no variable to inspect except self. The self is going to be a germ object to build the definition.
At the risk of answers all being defense of FactoryGirl, if your alternatives to FactoryGirl are DSLs to populate data, you are still going to have the problem of debugging support.
Alternatives include fixtures and just calling ActiveRecord to populate test data. Really FactoryGirl doesn't have much over ActiveRecord, it's just more symbol oriented calls, so people get to make meaningful symbol names which is all FactoryGirl was supposed to do.
You cannot put a debugger statement in the middle of a fixture, or in the middle of a hash that you are sending to a create method, but at least you won't be tempted to.
(The See also Factory Girl - Why are Records being continually created?, was an example where FactoryGirl was working perfectly, but the user told it to create four records, and then was surprised when it created four records.)
So maybe if you stick with fixtures and ActiveRecord calls things will be dumbed down enough that you won't get confused.

Rails test fixtures vs uniqueness

In Rails, fixtures records seem to be inserted & deleted in isolation for a given model. I'd like to test having many objects/rows in one transaction, eg. to check uniqueness. How to do it?
Fixtures are not validated. When you set them up they can be totally wrong and Rails won't complain until something blows up. It's a good idea to make sure your initial test DB (that is seeded with your fixtures) is in a valid state before tests are run.
For checking things like uniqueness, I would create the records on the fly and not rely on fixtures. Either create them right in your test case, or use something like FactoryGirl (which by the way, is a great way to clean up your tests and stop using fixtures completely).
Are you saying you want to build a test to check the rails "validates_uniqueness_of" operator or that you want to test the logic of your own unique record? In the first case, I wouldn't bother, the Rails tests cover that. In the second case, I would create a test that creates a record that is the same as one in the fixtures.
In the broader sense of putting multiple saves into a single transaction, you can create your objects and then:
MyModel.transaction do
model1.save
model2.save
end
but I don't think this is the way to accomplish either of the things it seems that you want to do.

Resources