Hey Myron,
I would certainly like something along the lines of
`aggregate_failures`. Having recently used Spock, the Groovy
equivalent to RSpec, they do some interesting things in this regard. I
don't fully grok how this syntax is possible, but it looks something
like this:
def "creates a amazon gift card"() {
when:
CreateGiftCardResponse response = client.createGiftCard(payment)
then:
response.status == Status.SUCCESS
response.creationRequestId == "123"
}
The then: area executes each expectation similar to how you describe
`aggregate_failures`.
Allen Madsen
http://www.allenmadsen.com
On Wed, Feb 11, 2015 at 10:47 PM, Myron Marston <[email protected]> wrote:
> On Wednesday, February 11, 2015 at 7:23:13 PM UTC-8, Jesse Whitham wrote:
>>
>> So I ran into this problem with Testing our API.
>>
>> The problem is the get request is called multiple times based on examples.
>> e.g this code below will run get 'test' twice.
>>
>> require 'rails_helper'
>>
>> describe API::TestController, type: controller do
>> before do
>> get 'test'
>> end
>>
>> it { expect(response).to be_ok }
>> it { expect(response.body).to eq('test code')
>> end
>>
>> This is a problem when you start to have more expect statements in terms
>> of performance. As far as I know there is no good workarounds for examples
>> to re use the same response. The guide herehttp://betterspecs.org/#single
>> talks about putting multiple expects into the it statement, this seems to go
>> against getting good failure responses.
>>
>> Using a before(:all) you get an error like so
>>
>> Failure/Error: get 'test'
>> RuntimeError:
>> @routes is nil: make sure you set it in your tests setup method.
>>
>> Is there a way to send only one request without ruining the failure
>> responses?
>> (or if you like use memoization over multiple examples)
>>
>> I did find you could use a global variable but this seems like the worst
>> code ever.
>>
>> require 'rails_helper'
>>
>> describe API::TestController, type: controller do
>> it 'makes a single request' do
>> get 'test'
>> $stupid_global = response
>> end
>> it { expect($stupid_global).to be_ok }
>> it { expect($stupid_global.body).to eq('test code')
>> end
>>
>>
>> I posted this here https://github.com/rspec/rspec-core/issues/1876 and got
>> this response:
>>
>> This conundrum (shared state vs performance is one of the reasons we added
>> compound matchers to RSpec 3.2, so you can now do:
>>
>>
>> it { expect(response).to be_ok.and eq 'test code' }
>>
>>
>> This isn't a complete solution of course but we don't want to advocate
>> shared state across examples.
>>
>> Incidentally Github issues are not the place to request support, please
>> use the mailing list / google group
>> (https://groups.google.com/forum/#!forum/rspec) and/or #rspec on freenode."
>>
>>
>> I really don't see this as a even usable solution as if you have 100
>> expectations
>>
>>
>> And you compound those you end up with failure in one string like so:
>>
>>
>> Failure/Error: "we expected it to have this and and we expected it to
>> have this and we expected it to have this and we expected it to have this
>> and we expected it to have this and we expected it to have this we expected
>> it to have this we expected it to have this we expected it to have this we
>> expected it to have this we expected it to have this we expected it to have
>> this"
>>
>> you don't compound them have one useless string with lots of expectations
>>
>> Failure/Error: "we expected the response to be ok (not sure why its not)"
>>
>> or you make 100 requests (massive performance load).
>>
>> Does anyone have any suggestions for better ways? Alternative testing
>> frameworks? (maybe rspec just isn't useful for this kind of testing) or even
>> a feature for shared state? (By the sounds of it this will not be supported)
>>
>
>
> Hey Jesse,
>
> This is a great question. One solution, which has been available for years,
> is to use a before(:context) (or before(:all) — that’s the old RSpec 2.x
> form, and it still works in RSpec 3) hook. See, for example, this PR where
> I’m doing a slow operation in before(:context), storing it in an instance
> variable, making it available via some attr_reader declarations, and using
> the results from multiple examples.
>
> Note, however that before(:context) hooks come with many caveats. (See the
> “Warning: before(:context)” section from our docs). The basic problem is
> that many things that integrate with RSpec — such as DB transactions from DB
> cleaner or rspec-rails, or the rspec-mocks test double life cycle — have a
> per-example life cycle, and running logic outside of that lifecycle can
> cause problems. If you create DB records in before(:context) and are using
> per-example DB transactions, it would create the records and not clean them
> up afterwords, potentially affecting later tests. So I’d say the
> before(:context) solution is great as long as you don’t have per-example
> life cycle stuff going on. If you do have that kind of stuff going on (and
> it’s very common to, especially in a rails context) you’re better off
> avoiding before(:context) or at least being extremely careful what you do in
> there.
>
> I think the “one expectation per example” guideline is a useful corrective
> to a pattern many first-time testers fall into, where they do too much in
> one test or one example, and have hard-to-understand test failures, but it's
> not something I recommend following strictly. Personally, I use “one
> expectation per example” as a signal…if I’m putting multiple expectations in
> one example I may be specifying multiple behaviors. In fast, isolated unit
> tests you want to keep each example focused on one behavior. In slower,
> integrated tests that’s far less important, and the cost of the setup time
> (and different kind of test) causes me to not worry about “one expectation
> per example”. If you are doing slow integrated testing and the thing being
> is so complicated that it needs 100 expectations (as per your hypothetical
> case), that suggests to me that your logic could benefit from being
> refactored, with more of it being extracted into stand-alone ruby objects
> that don’t interact with the slow external things and can be quickly unit
> tested in isolation.
>
> One other thing I’ve been mulling over recently is a new feature in RSpec
> that would better support what you’re trying to do. I’m thinking it would be
> something like:
>
> it "returns a successful response" do
> get 'test'
> aggregate_failures do
> expect(response).to be_ok
> expect(response.body).to eq("test code")
> end
> end
>
> The idea is that aggregate_failures (not necessarily what we’ll call it —
> it’s the best name I’ve thought of so far, though) will change how expect
> works for the duration of the block so that rather than aborting on first
> failure, it collects all expectation failures until the end of the example,
> and the block, and then, if there were any failures in the block, it’ll
> abort at that point with all of the failure output.
>
> Would that do what you want?
>
> HTH,
> Myron
>
> --
> You received this message because you are subscribed to the Google Groups
> "rspec" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> To post to this group, send email to [email protected].
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/rspec/27e919c9-930f-43d0-bedd-4c7aeed4ad4a%40googlegroups.com.
>
> For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups
"rspec" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/rspec/CAK-y3CtGcE4ucQsb9uHPccM59iHAxVYJAa3g-6xwzkevKBRYUw%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.