--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
To view this discussion on the web visit https://groups.google.com/d/msg/rails-oceania/-/Do3fM2YDVc0J.
To post to this group, send email to rails-...@googlegroups.com.
To unsubscribe from this group, send email to rails-oceani...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rails-oceania?hl=en.
class LineItem < AR
belongs_to :order
named_scope :paid, :conditions => { :paid => true }
end
it 'total_cost returns the total of all the line items' do
order = Order.create
order.line_items.create!(:cost => 10, :paid => true)
order.line_items.create!(:cost => 20, :paid => true)
order.line_items.create!(:cost => 50, :paid => false)
order.total_cost.should == 30
end
On 26/08/2011, at 2:09 PM, Scott Harvey wrote:
> The paid named_scope in the example is on the LineItem model so stubbing that out would not be stubbing out the object under test.
I clearly need more coffee! (or is it less coffee?)
> The test that checks that the paid named_scope works will be in the LineItem spec folder so I'm happy to stub the named_scope out if there is a nice way to do that.
In that case, something like this would completely avoid the database: (I haven't checked the syntax at all)
it 'total_cost returns the total of all the line items' do
order = Order.new
line_items = [stub(:cost => 10), stub(:cost => 20)]
order.stub(:line_items).and_return(stub(:paid => line_items))
order.total_cost.should == 30
end
You only need to stub the bits you're using, hence no paid methods on the line_item stubs. You also don't need to save the order object, you can just use the in memory representation.
def total_cost
line_items.paid.sum(&:cost)
end
end
It looks like stub_chain might help:
http://apidock.com/rspec/Spec/Mocks/Methods/stub_chain
I.e. (untested):
mock_line_item = mock("line_item")
mock_line_item.stub(:cost).and_return(10)
Order.stub_chain(:line_items, :paid).and_return([mock_line_item])
Still feels wrong though ...
Malc
Is more likely to work
Malc
As you mentioned it still feels a bit wrong as you end up testing the
implementation of the method instead of the behaviour.
It seems to be a choice between hitting the database and having slow
tests or stubbing out the method chain and having brittle tests.
Either way I'm not completely happy with the resulting test suite.
I would have thought this situation would be fairly common in a rails
application, how are people handling this currently?
Scott
I would have thought this situation would be fairly common in a rails
application, how are people handling this currently?
--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
That's the conclusion that I reached a while back as well which is why
I've been doing it for so long.
The thing that annoys me is I read through a book like Continuous
Testing (http://pragprog.com/book/rcctr/continuous-testing) and they
say you should be aiming for your tests to run fast enough to have
hundreds running per second.
That's all well and good for code that isn't hitting external services
(database, file system) but within a rails application it just doesn't
seem at all feasible and still keep a solid test suite.
If anyone has any more tips on when to mock/stub within a rails
application or how to speed up a test suite I would love to hear it.
Scott
--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
Is named scope incapable of working without hitting the database
1. When rails 3 was released it could not boot even though all tests passed. The reason was that boot-up process was stubbed (don't remember exact details though).
The thing that annoys me is I read through a book like Continuous
Testing (http://pragprog.com/book/rcctr/continuous-testing) and they
say you should be aiming for your tests to run fast enough to have
hundreds running per second.
And in certain circumstances this is exactly what you want to do, because you don't care about that part of your system.
Lately I've been doing the following:
Monkey patch ActiveRecord in spec_helper to raise if you try and write to the database, this forces you not to use it. It changes the way you structure some of your code, usually for the better, but ultimately it means you have a very fast unit test suite. These tests are fast, hundreds of tests per second fast.
Then in Cucumber I do everything through the user interface. If we need a user, we run the sign up process, then test whatever it was that required the user. Nothing gets into the database unless it came through the front end. This is slow, but exhaustively tests the full software stack. I tag every cucumber test with the model names and/or feature name involved, so that I can run the tests that I think might break locally before pushing, then I let a build server handle the full suite.
So far it's been working out well.
--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
To post to this group, send email to rails-...@googlegroups.com.
To unsubscribe from this group, send email to rails-oceani...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rails-oceania?hl=en.
Any chance you can share that patch so I can try it out for myself?
Scott
I guess this might be an argument for restructuring your Rails
application so your models stick to the Single Responsibility
Principle.
An approach to this was outlined by Yehuda in this Stack Overflow answer.
http://stackoverflow.com/questions/1068558/oo-design-in-rails-where-to-put-stuff/1071510#1071510
I haven't tried this myself or seen any other applications using this
pattern but it sounds good in principle and would decouple a lot of
classes from the database layer.
Scott
https://gist.github.com/1172821
There might be a nicer way to do this, but this does the trick for postgresql.
> --
> You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
- Stub the object under test. Benefits: Fast, simple. Risks: Stubbing
the object under test means that you're no longer testing the object
you think you're testing. This is a bad idea. It also
allows/encourages you to test implementation rather than behavior,
which is also a bad idea, because it makes your tests brittle.
- Stub database interactions at the driver level. Benefits: It's
fast. It allows for "true" unit tests. You don't have to stub the
object under test. Risks: It's a pain in the ass. See unit_record and
more modern friends. I've been on a project that did this with a large
test suite and I didn't much value the feedback those tests gave me.
ActiveRecord is too tied to the underlying database.
- Hit the database, but use a faster in-memory database (for example,
sqlite instead of mysql). Benefits: Speed. Risks: Slight differences
in the underlying database can ruin your tests, and your day. But if
you're not doing anything too implementation-specific then this can
work well.
- Hit the database as normal but parallelize your tests across
multiple processors and/or machines. This is my preferred approach
because it has the least impact on the test suite. Benefits: Speed, no
need to modify existing tests. Scales well as you add hardware. Helps
more than just database tests. Risks: Setup is annoying, potentially
difficult to configure, test runs may occasionally fail for funky
reasons (network I/O, e.g.).
HTH,
Brian
> --
> You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
To post to this group, send email to rails-...@googlegroups.com.
To unsubscribe from this group, send email to rails-oceani...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rails-oceania?hl=en.
Yeah, that's how I understand it.
I think naming the mirrored classes might get annoying but I'm sure
you could come up with some system that would work.
The other benefit I can see it that your 'mirrored' classes will only
implement the methods you need for your application so you would have
a clearly defined DSL for the application.
Again, it all sounds good in theory but I'm yet to see it in practice.
Scott
I could probably see a situation where you could use a fast database
locally like sqlite but then have your integration server (if you have
one) running your production database setup.
Scott
> So you keep your Rails models as simply a data-access later, and move allYeah, that's how I understand it.
> domain logic to a mirrored set of classes that are easy to unit test in
> isolation?
I think naming the mirrored classes might get annoying but I'm sure
you could come up with some system that would work.
The other benefit I can see it that your 'mirrored' classes will only
implement the methods you need for your application so you would have
a clearly defined DSL for the application.
Again, it all sounds good in theory but I'm yet to see it in practice.
Scott
--
You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group.
To post to this group, send email to rails-...@googlegroups.com.
To unsubscribe from this group, send email to rails-oceani...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/rails-oceania?hl=en.
I finally managed to find the article I read that suggested this
pattern, worth a read.
http://solnic.eu/2011/08/01/making-activerecord-models-thin.html
Scott
I've come to think of RSpecs as unit tests and I don't expect them to
test the "seams" or integration - their responsibility is to be fast
and test as much as is *practical*. My Cucumber scenarios act as
integration tests - their responsibility is to be exhaustive and
involve the whole stack working together in a way that is as close to
real life as possible. I've been burnt by prepackaged pickle setup
steps, etc. and have come to create all my data the "long way"
nowadays as Gareth describes.
I have certainly discovered tests that test more than one thing and
cause 100% rcov, and when I stub/mock them properly, code that should
have had its own unit tests but didn't has been "uncovered" (double
entendre?). Therefore, I feel that isolating unit tests better leads
to more thorough testing.
Also, I like the principle that I shouldn't be testing simple named
scopes - that is functionality you get from Rails and is tested there
already. In the same way that a unit test stubs out its collaborators,
I am happy to stub out something that is unit tested inside the Rails
framework.
--
Gregory McIntyre
So with that in mind how would you go about writing a unit test for
the example that I gave at the top of this thread?
Scott
Oh you want me to do more than mouth off? ;-X
I'd do something along these lines:
https://gist.github.com/1172955
--
Gregory McIntyre
Oh you want me to do more than mouth off? ;-X
I'd do something along these lines:
https://gist.github.com/1172955
Yep. I'm not sure of a better way to work around the paid scope.
I have been finding that liberal use of let allows me to put any
"required but you'd rather not have to" stubs that are a little bit
implementation aware (such as the paid stub in my gist) into a before
step very early on in the spec and what is left in each case is just
1) varying inputs (via let) and
2) outputs (via it { should })
that can be expanded rapidly and mixed together in combinations of
context blocks, without much extra stubbing/mocking.
I hope that makes sense. Happy to explain with more examples if it helps.
--
Gregory McIntyre
I hope that makes sense. Happy to explain with more examples if it helps.