#276 Testing Time & Web Requests
It can be difficult to test code that deals with the current time or an external web request. Here I show you how to do both using the Timecop and FakeWeb gems.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
Wow, allmost like voodoo. Was just about to question how I would test my external web requests properly and bam you come with this episode ;). Awesome!
The thing I might add for asynchronous requests (or requests that can time out) searched a lot around and the only proper way to make a request timeout seems to be as follows:
Using the above way your rails process does not hang if the request fails it just times out (had that problem with a backend that would sometimes fail to respond).
Kind regards and keep up the great work!
More test driven development episodes, it's awesome!
By the way the above does a post request (can do gets too which is commented) and it parses the uri including a port which can be something like http://example.com:5000. Parameters is a hash like:
{:file=>'bla.mp4'}
Thanks for this! - this is really useful - time/web mocking can make tests much much simpler and cleaner.
I'd also like to recommend WebMock
From what I gather, WebMock is a bit more fully-featured (it was even originally inspired by FakeWeb), and also works with non-Net::HTTP - based libraries such as EM-HTTP-Request
Thanks again
Mark
I can't remember now why I chose FakeWeb over WebMock when I used it in railscasts.com, but it definitely does look more full featured. Thanks for bringing this up. Time to research it.
Also have a look at VCR - a great testing tool for recording and playing back http interactions.
Thanks for the link! I do hope to cover VCR in a future episode which is more about testing with an external api.
Thanks for the introduction to Timecop. It didn't even occur to me that something like that would exist. Thanks!
Great screencast as always, thanks!
One possible improvement (at least for me) is the block usage of Timecop#freeze.
Instead of having to call #return explicitly, I prefer to put the frozen code in a block, like:
That way it automatically returns from the frozen time and also makes explicit which code is frozen via indentation.
I think it's still good to have a
Timecop.return
statement inspec_helper.rb
in case something slips through, but I do think the block syntax is great for larger test cases where you want to isolate where it is frozen.Excellent cast as always, and I really like the testing angle. I've got a question though and I was hoping you or anyone else could help me out. I fairly experienced in the app folder, but not quite as much in spec. But alas, I have taken on Ryan's challenge last week and haven't opened the browser since. It's been going great, and I have tons of test coverage, but I'm worried I may have too much - or at least redundant tests. Specifically, I see a lot of overlay between my integration tests and my controller tests. I find that whichever one I write first, it covers about 75% of the other one. That really bugs me. So a few things:
Thanks so much everyone.
Ken
I don't do controller or view tests because I feel they overlap too much with integration tests. Usually if something is too complex to go in an integration test then it should be moved into a model or helper and tested there in isolation. This makes it easier to handle a lot of branching paths and such as well.
I'm not very strict on refactoring test code, I feel it's better to be more direct there to reduce the possibility of bugs and odd dependencies. But when I do see a lot of duplication I like to move it into a module in the spec/support directory and do
config.include
in the spec_helper.rbOk interesting to know. I was trying to stick with that as well, but I really ran into trouble when I wanted to test redirects and urls. Maybe there's a way to do this (cleanly), but I couldn't figure it out. Say you wanted to test a very simple create action that redirects to the show page. In the integration test you have something like:
at this point, how can you access the controller variables to ensure that you redirect to user_path(@user). assigns(:user) wasn't working, and after days of searching, came to the conclusion this sort of test is best served in the controller specs. I supposed you could do something like:
current_page.should eq(user_path(User.last))
but that didn't seem right at all.
Ryan Bates is a God!!
My first impulse when watching the first part of the episode was "just mock the Time.zone method so that Time.zone.now returns a fixed date"
Wouldn't that have been enough?
I thought exactly the same. I always just mock Time.zone.now (or Time.now) to test if the time gets set.
I really would like to hear what the benefits of using timecop are if you only want to test if the time gets set to Time.zone.now
Hi Ryan, fantastic episode as alwais.
Do You know a way to test file uploads? e.g size, timeout, type of files (mime), image dimensions,.. ?
And where to put this stub files?
Your just too awesome !! I am blind with true awesomeness :)
Testing with times involved is mostly no fun, particularly because I never heard of Timecop before :) But I found a way to manage it with RSpec matchers:
That's what I've been doing as well. I'm giving Timecop a try now and it's pretty good, not much changed except a few tests so far.
It can have unpredictable behaviour. What if send_password_reset first sets attribute value and then does something longer than a second due to high system load? What if send_password_reset doesn't really set attribute value but some previous test does and tests are not fully isolated? You'll have test that breaks from time to time in random manner.
You're right. The Timecop version is more deterministic and that is what our tests should be.
But there maybe situations where you can't use Timecop and you don't need to test on milliseconds (with an increased delta to get a more robust test), then it might be a good solution too.
I can't believe no one has taken to calling you Dr. Bates yet. Not just for your coding examples, but also because of your ESP in delivering these episodes exactly when others are in need of them. Thanks again!
I can't get the timecop to work in my application.
I hope you guys know what's wrong. I've used Ryans code.
What I see is that the reloaded object has nil for the password_reset_sent_at attribute.
I get:
1) User#send_password_reset saves the time the password reset was sent
Failure/Error: user.reload.password_reset_sent_at.should eq(Time.zone.now)
stdout:
printing user password_reset_sent_at
Fri, 31 Dec -0001 23:40:28 UTC +00:00
printing reloaded user password_reset_sent_at
nil
printing last user password_reset_sent_at
nil
I found that some error while testing with cucumber and fakeweb while you have @javascript tag on cucumber. It time out for some reason. Here is the issue https://github.com/chrisk/fakeweb/issues/25
here is my sollution http://ahmy.yulrizka.com/2011/08/cucumber-timeouterror-timeouterror-on-fakeweb/
Thanks Ryan!
Another alternative for faking Time is Delorean. It's quite similar to Timecop, although it does not offer the freeze capabilities. Check it out, its code is really minimal and beautiful!
https://github.com/bebanjo/delorean
This is certainly my favorite code excerpt:
```ruby
Always a new stuff, thanks Ryan.
Simple
Timecop.freeze
doesn't work with Oracle db, because:ORA-01841: (full) year must be between -4713 and +9999, and not be 0
A time must be specified for example
Timecop.freeze(DateTime.now)
Another option of RSpec-ing across multiple time zones:
http://approache.com/blog/testing-rails-across-time-zones/
I came across this today when looking to test ActiveResource and it proved to work really nicely, however I then came across ActiveResource::HttpMock which is a specific mocking implementation for active resource which was even easier to get up and running.
Thanks for the cast though, set me on the right path of mocking the web request rather than the ActiveResource itself.