WestMichiganRubyTraining / discussion

Create issues on this repository to open discussion threads in the Issue Tracker!
2 stars 0 forks source link

More testing questions #57

Open ThomasBush opened 10 years ago

ThomasBush commented 10 years ago

I am using rspec and FactoryGirl. I have written some basic specs to test that a FactoryGirl instance contains correct data.

I am trying to test more functionality than these simple fields, like has_many through associations for example. For example I have sites and products, each has_many through products_sites. How I am currently testing this association would look like the following.

  it "should have relationship with products" do
    expect(@site).to respond_to(:products)
  end

I guess this may be my lack of experience, but is this test even valid? It passes, but I'm not confident that I am actually proving what I am intending to. I have seem SO questions/answers where people recommend Shoulda gem by thoughtbot. I guess I am starting to get worried that I am just throwing gems at my problems now. Could anyone experienced with testing rails apps, especially has-many-through associations and polymorphics weigh in here. I would really benefit from hearing about the gems you use and how you test these associations. I want to learn how to do this correctly, it just seems there is so much out there to use that its a bit overwhelming. I could benefit from an experience opinion here so I could focus in.

Also a side question, I read/hear people talking about not writing tests ( like view specs for example). Are some tests more important than others? I am trying to figure out where to focus my attention and learning.

Thanks!

thejbsmith commented 10 years ago

@ThomasBush , I would highly recommend Shoulda. It's written by ThoughBot, and they really know their stuff. Yes, you can write your own specs to test associations and validations, but why waste your time on writing those specs (which you will write over and over again) when you can use a gem that already has it figured out?

An example of a has_many through test in Rspec with Shoulda:

describe Post do
  it { should have_many(:categories).through(:other_model) }
end

Ruby/Rails and gems are meant to make your life easier and your coding more productive. But this can also be a double-edged sword. I have seen too many developers completely rely on gems and not understand what is going on when something breaks. In your application, if you feel that way then it might be a good idea to figure it out (at least partially) for yourself before throwing gems at. In the case of testing, I don't think this is nearly as important. No reason to spend more time figuring out how to test your code.

People will give you many different answers on which tests are important and which are not. I believe view specs can be useful, but I think model and controller specs are much more useful. If your model and controller don't work correctly, you can't expect your view to work correctly. Which tests you write will ultimately be up to you and what you feel comfortable with. You can write view, request, feature, route, etc. specs but at some point I believe it becomes a bit overkill.

Now, all this is just my opinion. I would love to hear others as well.

billgathen commented 10 years ago

I've never been one to write view specs, but I've been getting a lot of mileage out of integration specs, which can accomplish some of the things view specs would, plus all the navigation/workflow stuff you can't get any other way.

Combining a few "happy path" integration specs with unit tests underneath to make sure you've covered your edge cases has worked well for me.

The capybara gem works great for integration-testing rails. The "Using Capybara with RSpec" section of the readme gives a good overview of the recommended approach. It essentially allows you to simulate a user sitting with your app and trying things out, which ensures you've wired your pieces together properly as well as proving you coded them correctly.

ThomasBush commented 10 years ago

Thanks @thejbsmith and @billgathen for the helpful responses. I knew I was asking a bit of a loaded question there when asking what is more important, but figured that would encourage others to give their input. I feel like I am still at the point where I don't know what I don't know with testing, if that makes sense. So I am trying to figure out where to focus my energy. This gives me some gems I can dive into.

Any other process tips you have? For example Combining...integration specs with unit tests or why waste your time on writing those specs...when you can use a gem that already has it figured out These are helpful in explaining how these things all work together and make the larger picture a bit less murky for me. I would love to hear from others as well! Thanks guys, helpful as always.

toreyheinz commented 10 years ago

Here's my thoughts on testing in general. I want behavior driven tests, and I want my tests to drive my development. So in the case you proposed it feels like writing tests, just to have tests.

Often times the setup for a many to many test can be messy, because to test it fully you need 2 of one item & 3 of the other (I could be wrong though... anybody?)

Here's a scenario that may... demonstrate how to let your behavior drive your tests/development.

Start outside in with a feature spec (I've been using Rspec for this lately), then inside your feature start with your expectation.

visit site_products_path(site_1)
expect(page).to have_content(product_1.title)
expect(page).to have_content(product_2.title)
expect(page).to_not have_content(product_3.title)

visit site_products_path(site_2)
expect(page).to have_content(product_1.title)
expect(page).to have_content(product_3.title)
expect(page).to_not have_content(product_2.title)

Then setup what you need meet the expectation (above the previous code)

let(:product_1) { create(:product) }
let(:product_2) { create(:product) }
let(:product_3) { create(:product) }
let(:site_1)      { create(:site, products: [product_1, product_2]) }
let(:site_2)      { create(:site, products: [product_1, product_3]) }

Run your tests, if you don't have the association setup you'll get a nice error message really fast! But don't just go setup the association, drop down to a unit test that expresses the desired behavior of adding products to sites. The setup be similar to the feature except I want to add the products "manually"

it 'should has a many to many relationship with products' do
  # This setup would probably get moved up, and shared
  let(:product_1) { create(:product) }
  let(:product_2) { create(:product) }
  let(:product_3) { create(:product) }
  let(:site_1)      { create(:site) }
  let(:site_2)      { create(:site) }

  site_1.products << product_1
  site_1.products << product_2
  expect(site_1.products).to include(product_1, product_2)

  site_2.products << product_1
  site_2.products << product_3
  expect(site_2.products).to include(product_1, product_3)
end

Now making this test pass would ensure the relationship behaves as you expect (or maybe not, if not change the test), and the "only" way to make it pass is to create the relationship. It's also kind of nice that it's not worried about how it's actually implemented, maybe you switch to Mongoid, and don't use a has_many through, you leave the test the same as long as the Model behaves the same.

Also once this passes you can go back and implement the Controller/View logic to make the feature test pass.

Testing in general is hard, I still fight the urge to skip it... but it's worth it, stick with it! I would say it took me two years... maybe more before I felt comfortable with it.

One more also ;) you could change the above test to stress the bi-directional relationship, but then you might want call it a sites_products_relation_spec

  site_1.products << product_1
  site_1.products << product_2
  expect(site_1.products).to include(product_1, product_2)

  product_1.sites << site_2
  product_3.sites << site_2
  expect(site_2.products).to include(product_1, product_3)

  expect(product_1.sites).to include(site_1, site_2)
ThomasBush commented 10 years ago

Thanks @toreyheinz it seemed off to me and when you said it felt like writing tests just to have tests I realized that was it. This was really helpful so thank you!