I have a rails application that uses Recurly for its transactions. I am trying to write automated tests for some of the helper functions that I have written.
A super simple example of a function...
def status_for_display
transaction.status.capitalize
end
In order to test these functions, I need to have a Recurly::Account object as well as associated Recurly::Transaction objects.
I have tried going the route of using Recurly::Account.create & Recurly::Transaction.create but I cannot seem to get the transactions to match up with the account.
I am also wondering if it doesn't just make better sense to use the VCR gem to make this happen. In which case, how would I go about doing that? I've never really managed to get VCR setup properly.
VCR is, by in large, plug and play. Once you have it configured and enabled, it'll intercept all HTTP requests and try to play back the data from a cassette. The problem with VCR, though, is that it is request data specific. In order for it to work right, you need to ensure that your tests are always sending the exact same request params to Recurly. You can work around this by having it skip certain things, but it's generally a pain.
The other option is to just use something like Webmock directly and house your own "known responses" for your Recurly calls, but then it's up to you to ensure that you responses stay in sync with the API.
In the end, I'd probably recommend going the VCR route but structuring your tests such that you have known good and bad test scenarios so you can get the real benefits of the cassettes.
Related
I read that controller specs are going out of favor and that the current recommendation is to use feature and request specs. I've found feature specs have been working really well for happy path testing, but I'm having some difficulty with exceptional cases I want to test. For instance I have an index of resources owned by a user. This list includes buttons to destroy each resource. Users only see their own resources. I want to add a test that attempting to destroy one that belongs to someone else fails. The problem is that the list won't include anybody else's items so there isn't an appropriate link to tell capybara to click. In a controller test I could directly issue a DELETE on the resource's path, but as best I can tell that is not an option in feature specs. Am I missing something? Do I need to implement request specs too if I want coverage outside the happy path? I was hoping to reserve request specs for covering my API endpoints.
From my point of view, permission specs should NOT be implemented as feature specs. Your thoughts fully makes sense and the only one thing you can really check there is that you DON'T see any entities, that were made by other users. Your logical difficulties is like +1 point, that such scenarios should be tested via another spec types. Also... feature specs are expensive and I would never do such 'deep' test in features.
I never before worked with API in Ruby on Rails so I'm pretty lost.
WHERE and HOW do I need to put this code.
GET http://api.bigcartel.com/mystore/products.json
Do I need to make a new products model and then write the API somewhere in application.yml
Thanks
There are a few ways you can work with an API from ruby. First if you are lucky maybe somebody already built a wrapper for that specific API. In this case there is one gem but it does not seem to be maintained any more. It might still work so feel free to try it out.
However you can also make HTTP requests by yourself. I prefer to use HTTParty gem. You could define a new class in /lib directory and build an API wrapper yourself, but let's start with a simple example.
response = HTTParty.get('http://api.bigcartel.com/mystore/products.json')
parsed_response_body = JSON.parse(response.body)
So here we first create an HTTP get request, and then parse the body to turn JSON into ruby hash.
Please notice that this is a very minimal example that assumes that everything will always go right. APIs often fail and unexpected things happen, don't forget to catch and handle exceptions that might occur.
A project that I am working on has integration tests which actually go out and hit a 3rd party api over the wire... Running these tests takes a very long time. I suggested that we implement something like VCR so that the data the tests interact with can be captured as fixtures, and improve reliability and speed of these tests.
I don't have access to the codebase at this moment, but I believe the tests were doing something like this:
before do
login_some_user
end
after do
make_web_request_to_clear_items_in_cart
end
it "adds some items to the user's cart" do
make_web_request_to_add_item_to_a_cart
end
So basically the before block was making a web request, the example was making a totally different request, and an after block (which I know is not ideal to use) made a 3rd request to clean up records that had been created by the example.
I setup an around block in spec_helper that captures web requests and stores them named after the example. However, after running the tests repeatedly I found that they became extremely flakey, sometimes passing, sometimes not.. I tried wrapping the before and after blocks in a separate VCR.use_cassette block call, but it made no difference.
I am wondering if I am missing something, and if there's a way to handle multiple requests like this or what?
VCR can handle multiple requests. From you pseudo-code, you would have the following:
it "adds some items to the user's cart" do
VCR.use_cassette "your_path/cassette_name" do
login_some_user
make_web_request_to_add_item_to_a_cart
make_web_request_to_clear_items_in_cart
end
end
The cassette your_path/cassette_name would contain all 3 web requests.
Your issue likely comes from using before and after blocks. See Myron Marston explanation of how VCR handles the before(:all) hook.
I'm currently creating rspec test cases for a Rails 2.3 application. The problem I'm confronting is that the application has a dependency on data from an external database like this:
Main_User is an external application.
Node_User is the application I'm testing.
When a new Node_User is created, Node_User calls Main_User which creates a user and returns with some data which Node_User uses to create a the Node_User.
Is there any way I can query the Main_User database to verify that the record created there contains the data that is passed to Node_User? Or is there a better way to test this?
Thank you!
Yes, you can theoretically query the remote database if you have access to the user/pass/hostname for that database. (Search for "Rails multiple databases.")
But I wouldn't necessarily recommend taking that approach. Your tests would be modifying the state of the remote database. This strikes me as unsafe. You'll be creating bogus records--and possibly deleting or corrupting data, if your tests do any mass deletes or exercise broken functionality. In other words, your tests may do bad things to a production database.
Therefore, I would recommend mocking the remote database in some way. One option would be VCR. It's really cool, but it would require you to operate on the remote database at least once, because it has to record the first run. Another option is to use one of the tools underlying VCR, such as FakeWeb.
Of course, this only tests the Node_User side of things. You said you also need to verify that the remote record is correct. To do that, I would recommend writing tests on the Main_User side. Such tests would simulate an incoming RESTful request from Node_User and verify the correct result.
That only works if Main_User is your application. If it's a third-party service, then there may not be a safe way to test it. You may just have to trust its correctness.
We've recently started using VCR to stub requests in our cucumber tests. We're using cucumber tables to describe different kinds of requests, and storing them as variables with gherkin. Our cassette files have all been reworked to include erb, so that we can substitute in the values for the request we want to test.
The problem is that whenever there is a new request, VCR records the new requests and also overwrites (removes) all the erb from the cassette, replacing it with the request as interpolated for that example. Every time we run requests where a value has changed (say, the value of the timestamp we receive from the API we're talking to), all the erb needs to be copied back in to the cassette file. This is frustrating, since tests are run all the time.
Does anyone know why VCR strips the erb out when recording new responses? Any idea of a workaround? Is there any way to save back a template before it's interpolated?
Automatically merging your manual changes to the cassette with a newly recorded cassette is a difficult problem that, in my opinion, is best left to tools that are specifically designed to handle the history of text documents and manage merging them (i.e. your source control system). ERB makes it even more difficult: while you may just be using ERB to interpolate variables, any valid ruby can be used. There may be loops, conditionals and more in the ERB. There's no way for VCR to automatically merge this stuff.
A couple suggestions:
Use your cassettes in a more granular fashion so that they do not get overridden frequently. I tend to use a casette for each HTTP request or logical group of HTTP requests.
Use the :once record mode (the recent default) to prevent existing cassettes from being overriden.
Take a look at the sensitive data filtering feature of VCR. This is far less flexible than ERB (i.e. you can't use loops, conditionals, or any arbitrary ruby code), but for simple variable intepolation, it works really, really well. Rather than using ERB, it uses a placeholder string--you might use a string like <PASSWORD> to take the place of a password, for example. VCR automatically takes care of inserting the placeholder text when it records the cassette, and replacing it with the correct real value on playback.
If that doesn't meet your needs, the before_record and before_playback hooks should give you all the power you need to do automatic interpolation (or even ERB snippet insertion) when a cassette is recorded. It's basically how the filtering works--see here.
I'm certainly open to ideas for how to improve VCR if these things don't meet your needs.