Rails unit testing associations - ruby-on-rails

Should I write unit tests for my associations?
I haven't found many good resources on how and whether to do the testing. I also see/hear some opinion that it is okay to not test your associations (belongs_to and has_many) as they are already tested in rails. And there is another view that says, if it code you write, it is code you test.
So if you say I should, please tell me few good ways of doing this.
Currently, I'm writing tests using Test::Unit and I'm not using Shoulda (I don't have any macros). So for testing each association, I am creating a bunch of objects and then doing asserts on them. Somewhat like this -
For a Post model that has_many comments, my test logic goes this way -
p = Post.create(:title => 'dummy_title', :content => 'lorem ...')
3.times{ Comment.create(:post_id :=> p.id, :commentor => 'jack')}
assert_equal(3, p.comments.size, "post doesn't have correct no of comments")

To me, testing the association borders on testing the language (or in this case, testing the framework).
I'd reserve it for circumstances where you're doing something nonstandard with the association. For example, if every time you create a comment it changes something in the parent post, test that.

Related

How do i check the valid association and the related model existence

I am following this pattern
it { is_expected.to respond_to(:cars) } for checking the association
Now accidentally in the model, let's assume I have this line
has_many :bars
So in the test, after seeing the model. I went ahead and did this
it { is_expected.to respond_to(:bars) }
And the test passes, but there is no model Bar how do we rectify this.
has_many :bars adds many methods to your model, bars is one of them, and respond_to matcher just check if there's such publicly available method - so you don't really testing what you think you would like to test.
You could for example do something like this:
expect(subject.bars.build).to be_instance_of(Bar)
This spec would fail if Bar does not exist.
I'm also pretty sure that
expect(subject.bars).to eq []
Would fail too, since it should try to find those in the DB, and missing model would surface here as well.
Also this
expect{ subject.bars.build }.not_to raise_exception
should also fail.
That's generally the problem with dynamic languages - errors (including typos) are not immediately caught.
I actually didn't know that you can define a relation to unexisting model. But it makes sense - checking if the classes exists during the class definition could be a bit too heavy.
I have an answer in a few parts. First, you can help avoid these typos of errors by test-driving your implementation. If you write a failing test first, the error message is another opportunity for you to recognize the typo: "Wait, a minute... it doesn't make sense for this to respond to bars. That's not what I meant..."
The larger answer is that this test on its own has so little value as to be worthless. If you make the typo above in both the implementation and that test and no other test fails then it's hard for me to believe it matters at all if the object response to cars, bars or any other typo.
A more valuable test would exercise the behavior of the association. For instance, if you wanted to test that a Person could associate a Car to their user, you could start with a feature test that exercises that feature. The test failure would guide you toward a working implementation that may or may not require that association. But if you typo the association you will find out because you are testing actual behavior.

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

When is a good time to test associations with Shoulda?

With RSpec and Shoulda you can:
it { should belong_to(:product) }
I am told specs should specify observed behavior. This spec also does seem like duplication of code that can be written in the model. So is there a time and place to actually use a test like this?
The bigger question is, why is it bad to test this? If you are told specs should specify observed behaviour, and a model having a belongs_to automatically gives it a method to access the association, is that not something to observe? You could test the #product method instead, but how would that test go?
it "has an association to a product" do
product = Product.create
model = Model.create(:product_id => product.id)
model.product.should eq product
end
Is that really better than just using the single liner?
it { should belong_to(:product) }
If the code is in any way important, you should test it.
Furthermore, if you were following TDD, you would write the test first to specify that an association has to be there, then put in the code to maintain that test.

Lots of different models in tests (rspec)? Advanced

I am only looking for answers from senior/more experienced Ruby/Rails developers on this one, since I think this is a bit more advanced of a question.
I have a gem I am working on that adds some behavior to AR models. I have to test it for a lot of different associations (has_many, habtm, has_one etc), and I also have to test behavior when different options are passed for the associations (e.g. :foreign_key). Now with all these different models, I can use the same table in the database because the fields themselves do not need to change, only behavior specified through has_many, belongs_to and so on.
Keep in mind there are a lot of different options, so the number of models is quite large.
First I don't think it would be bad practice to have the definition of the models next to / in the test itself, for readability purposes (if I have multiple tests that use the same model then I would group them together and use the before method). So this is one of my goals, and you can comment on this if you don't agree.
The second thing I am not sure of is I wanted to keep the simple/same name of the model in all the tests, for example "Task", instead of TaskWithManySubtasksAndForeignKey or something ugly like that. The problem is there are so many models it's hard to come up with meaningful and simple names. I'm not quite sure about this - using the same name, since it's a constant, is a little problematic. I have a solution with a proxy class but I don't think this is the optimal solution. I was considering using variables (with the let method) like "taskModel", but it seemed a little verbose and unusual.
One other option that comes to mind, but I am not sure is possible to do easily, is to remove an existing association and then define a new one. So e.g. add a has_many and then remove it, add a habtm...
How would you go about doing this?
Defining unique models in the spec files is not necessarily a bad idea since it makes it easy to see exactly how each model is defined. The obvious problem with this approach is if you want to reuse the models in other test files. The Rails approach to this is to define all the models in separate files and then just require them in the tests that need it.
I think it really just depends on how many models you have and how much you want to reuse. In one of my gems, I took the approach of defining the models in the spec file, in another gem, I defined them in the spec helper, and in yet another I took the Rails approach and used a separate directory for them. If you asked me which one I preferred, I'd probably go with the spec that also contains the models because it's all in one place. Definitely a subjective problem though.
Another approach I've taken on occasion is to create an anonymous class that's guaranteed to only be around for the life of that test:
describe 'my test' do
let(:my_class) do
Class.new(Task) do
has_many :things
belongs_to :something_else
end
end
it 'should have many things' do
my_class.should have(100).things
end
end

has_many :through in fixtures in rails 3.1

I want to set up some has_many, :through objects using fixtures for testing in rails 3.1.
If you look at the documentation on the association as an example, I want to know how to assign assemblies to parts. The only way I've found to do it is to explicitly create the manifest object in it's own file, but if I'm happy with the defaults on that model and all I want to specify are the part/assembly id's then is there a way to do it directly?
http://guides.rubyonrails.org/association_basics.html#choosing-between-has_many-through-and-has_and_belongs_to_many
I want something like:
my_assembly:
parts: my_first_part, my_second_part
This works if you do HABTM, but not when you have the explicit join model.
Assuming you want or have to stick with fixtures, then if you switch from an HABTM to a has_many :through relationship, then you can no longer use the short inline lists. You must create a separate fixture file for the :through model.
Never, ever, use Rails fixtures for anything.
I'll repeat: Never, ever, use Rails fixtures for anything.
They are dangerous, cumbersome and make your tests leak state. They are unsuitable for writing proper tests. At best, you get tests that look as if they are properly written, but have hidden dependencies. The Rails team got this feature 100% wrong, and I wish they'd remove it from Rails so people wouldn't be tempted to use it.
Instead, install factory_girl_rails and use factories to create your testing records on demand:
Factory :assembly, :parts => [Factory(:part, :name => 'first'), Factory(:part, :name => 'second')]

Resources