Build up help for TDD/BDD with RSpec - ruby-on-rails

For about 2 weeks ago i've started learning Ruby, i've made an WebParser / Page WordCounter using 'open-uri' and 'nokogiri'. So i just run up the terminal with 'ruby counter.rb http://test.com word' and i and up with the number of matches of that word, case insensitive so i can grab everything.
SO here I am learning about RSpec, TDD, BDD and all this stuff, and i would like to know how my code could be constructed using RSpec examples and expectations. Ive already read all the documentation, i'm building simples examples to test, etc.
I would like to know if there is anyone that could build my code into RSpec examples and expectations, so I can study what you've done and how you've done.
Here is my code:
require 'open-uri'
require 'nokogiri'
class Counter
def initialize(url)
#url = url
end
def count(word, url)
doc = Nokogiri::HTML(open(url))
doc.css('head').remove
doc.text.scan(/#{word}/i).size
end
end
url, word = ARGV
puts "Found: #{Counter.new(url).count(word, url)} matches."
Hope someone could help me, i'm really into ruby and found this RSpec amazing,
Thanks guys, i'll be studying and waiting!

there's an rspec --init command that will create your boilerplate.
Once you've done this, open spec_helper.rb and require your code file.
By the way, it's a little odd that your initialize accepts a url and assigns it to an instance variable, but the count method takes a url as an argument.
So assuming it's refactored to this:
attr_reader :url
def initialize(url)
#url = url
end
def count(word)
doc = Nokogiri::HTML(open(url)) # this uses the attr_reader
doc.css('head').remove
doc.text.scan(/#{word}/i).size
end
Then you can write a test case like this (this is not a comprehensive coverage, just an example):
describe "Counter" do
let(:url) { "http://some_url" }
let(:counter) { Counter.new url }
it "counts words" do
expect(counter.count("foo")).to(
eq("<whatever the expected result is>")
)
end
end
Using let to set variables is optional. You could also set the variables inside the it ... do block, but you'd have to repeat it for each case.
In addition to .to you have .not_to, and there are many other helpful methods than eq. I recommend reading the RSpec matcher docs to familiarize yourself with these.
It's also worth mentioning that this test case will make an HTTP call which is sometimes desired, and sometimes not. For example if you have many cases and want to run them quickly, then removing HTTP calls will be beneficial. But doing this means that you are no longer actually testing the state of the url. What if the markup changes? Unless your test case actually makes the HTTP call, you won't know for sure.
Still, it's good to know how to remove the HTTP call since the underlying concept ("mocking" or "stubbing") has a lot of uses. Something like this:
it "counts words" do
mock_html = <<-HTML
<!doctype html>
<html lang='en'>
<head></head>
<body>foo</body>
</html>
HTML
expect(Object).to(
receive(:open).with(any_args).at_least(1).times.and_return(mock_html)
)
expect(counter.count("foo")).to eq(1)
expect(counter.count("bar")).to eq(0)
end
any_args is a special term you can use when stubbing methods. You could also use url here since you know what the passed argument is going to be.
For more info, again I'll refer you to RSpec's docs, this time the ones about mocking / stubbing.
Generally you want to focus mostly on input/output of functions. Sometimes you will want to check that another method is called (you'd use mock/stub for this) but it's probably not expected of you to test every single line of code.

Related

RSpec - Using `let` in spec helper

I have a large model that takes time to initialize in my RSpec tests.
I want it potentially available to every example, but want to only load it if an example requires it.
This seems like the perfect use for let()'s lazy loading - only load it when you need it.
In any particular spec file I can do
require "spec_helper"
feature "foo" do
let(:big_class) { MyBigClass.new(bar) }
...
end
This will make big_class available to every example in that spec file.
Is there a way to make this more global so that EVERY spec file and example can use it? I couldn't find a good way to initialize let inside the spec helper.
You may simply define a shared context and include it in every example. Regarding your particular question, it should look like following:
RSpec.shared_context "Global helpers" do
let(:big_class) { MyBigClass.new(bar) }
end
RSpec.configure do |config|
config.include_context "Global helpers"
end
However, it's rarely a good idea to include a shared context in all examples, and that big_class helper from your question really looks like something domain-specific. You can steer the shared context inclusion by metadata, for example when you want to include given shared context in feature specs only (they all have :type => :feature metadata set by default), you can do it this way:
RSpec.configure do |config|
config.include_context "Feature spec helpers", :type => :feature
end
You might consider other approaches:
Use mock objects instead of real ones.
Refactor the initializer and extract the slow operation to another method
Mock objects of course bring their own set of drawbacks; they can become stale and make tests more brittle. But for some tests that is not an issue.
Refactoring initializers is a favorite of mine. E.g.
MyBigObject.new(args)
becomes
MyBigObject.new(args).setup
or :load_data or :connect_to_database_on_the_moon or whatever is taking a long time. You get the picture.
Obviously this means changing your code, but I find that often works out to be helpful in other ways, and it certainly makes testing easier.
You don't want to use let. From the docs:
Use let to define a memoized helper method. The value will be cached across
multiple calls in the same example but not across examples.
You'll end up instantiating MyBigClass lots of times. I would recommend creating a global helper method somewhere in spec_helper.rb (or similar) that used memo-ization on it's own to return the cached value if it's already been setup.
Also be very careful with all this as you're violating the rule of isolated tests. Might be fine for what you're doing, but it's a red flag.
Use global before hook with an instance variable
From the docs:
RSpec.configure do |c|
c.before(:example) { #big_class = MyBigClass.new(bar) }
end
RSpec.describe MyExample do
it { expect(#big_class).to eql(MyBigClass.new(bar)) } # This code will pass
end
For more details check the suggestions in this answer

Source code for rspec "describe" method (and others)?

I'm sauntering through Michael Hartl's Rails Tutorial right now, and am finding that I'm constantly encouraged to use wonderful methods that inexplicably do amazing things. He does a generally competent job of explaining what they do, but there is no real nitty gritty of why and how they work.
Specifically, I have just been plundering the rspec gem on github searching for the source code to the "describe" method. I cannot find it. Having now read a large amount of the source code (at an apprehension rate of about 25%) searching for it, I know that once found, I will need to look at its parent classes and modules to understand a certain amount of inheritance before I can really grasp (and then never let go of) the flesh and bones of "describe".
I don't mind struggling to grasp the concept, I'm a fan of attempting to read code in new languages before I fully understand it so that I can read it again later and use the comparison of my comprehension as a gauge of my fluency. I'd just like a kicker. Either a description or a file location with maybe a little helper hint to get me started.
For example...
I found this:
# RSpec.describe "something" do # << This describe method is defined in
# # << RSpec::Core::DSL, included in the
# # << global namespace (optional)
and rpsec/core/dsl states:
# DSL defines methods to group examples, most notably `describe`,
# and exposes them as class methods of {RSpec}. They can also be
# exposed globally (on `main` and instances of `Module`) through
# the {Configuration} option `expose_dsl_globally`.
but then there is no "class Describe" or def "describe" or such in that file.
SO: can anyone tell me where the "describe" method is, how it works, exactly, or (if not) why I am naively searching for the wrong thing in the wrong locations?
As you may know, there is no difference between describe and context methods and you can use them interchangably. Rspec developers could not let themselves to repeat the same code for different method names, so they moved the declaration to
module RSpec
module Core
class ExampleGroup
def self.define_example_group_method(name, metadata={})
# here they really define a method with given name using ruby metaprogramming
define_singleton_method(name) do |*args, &example_group_block|
And call that method a bit later for all the same-functionality DSL methods:
define_example_group_method :example_group
define_example_group_method :describe
define_example_group_method :context
So in case you are looking for describe method source, dive into define_example_group_method with assumption that name argument equals to describe and example_group_block is your block body.
The RSpec code base is not a trivial thing to get your head round. However, these links should get you started ...
This line defines the describe keyword:
https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/example_group.rb#L246
The method above that line does the heavy lifting for you. Take your time reading it.
This part then exposes the generated method:
https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/dsl.rb#L54
Good luck!

Testing a call to an external library in MiniTest

I am working on an application for a blog using Ruby on Rails. I have a model called Essay with a Draper Decorator. I am also using MiniTest::Spec for testing this application. Each Essay has a body which will be stored as Markdown. In the EssayDecorator, I have a method called body which renders the Markdown to html using RedCarpet.
In order to test this method, I wrote the following code:
describe '#body' do
it 'returns html from the markdown' do
essay = FactoryGirl.create(:essay)
#decorated_essay = essay.decorate
markdown = Minitest::Mock.new
#decorated_essay.stub :markdown, markdown do
markdown.expect :render, "<p>Test</p>", [essay.body]
#decorated_essay.send(:body)
markdown.verify
end
end
end
And inside the decorator I have two methods:
def body
markdown.render(model.body).html_safe
end
def markdown
Redcarpet::Markdown.new(Redcarpet::Render::HTML, :autolink => true, :space_after_headers => true)
end
This test passes, but seems weird to me. I don't want to test that RedCarpet is doing its job, I just want to test that I call the render method.
Is there a best practice for mocking out this kind of thing in MiniTest? I am pretty new to using Mocks and very new to using MiniTest.
Thanks, in advance.
IMO this code seems weird because it is not testing the behavior of your code, but it is testing the implementation. If the implementation changed (you stored the HTML in a cache instead of running it through Redcarpet) then this test would fail. This looks over-mocked to me.
I don't want to test that RedCarpet is doing its job, I just want to test that I call the render method.
This is implementation. Why are you running the body through markdown? Because you want hyperlinks to be created from URLs? I'd create a test for that. You want to ensure links have the no-follow attribute? I'd create a test for that. Create tests for why the code is doing something, not how it is doing it.
It is also entirely possible you are missing an abstraction in your application. Something that is responsible for formatting the plain text into HTML. Something that uses Redcarpet or RDiscount or any other library deemed important. The EssayDecorator probably shouldn't be responsible for ensuring that the text was formatted properly, but it may be responsible for telling the right object to apply the formatting.

How to access a controller constant in an rspec test under Rails 3

Using Rails 3.2 I have a controller in a subdirectory (e.g. /controllers/data_feeds/acme_feed_controller.rb)
This controller has some constants as below
class DataFeeds::AcmeFeedController < ApplicationController
MY_CONSTANT = "hi
def do_something do
...
end
end
In my rspec controller spec (which is in /spec/controllers/data_feeds/acme_feed_controller_spec.rb) I want to access that constant and below are two ways I've tried it (both commented out in the code below)
describe AcmeFeedController do
if "tests something" do
#c = AcmeFeedController.MY_CONSTANT
#c = DataFeeds::AcmeFeedController.MY_CONSTANT
end
end
I'm clearly not understanding something about the scope in which the spec test is run. What do I need to do and equally important why (i.e. what's happening with the scopes).
Thanks for your help.
Constants cannot be referenced with dot syntax, so DataFeeds::AcmeFeedController.MY_CONSTANT would never work in any context. You need to use :: to reference constants: DataFeeds::AcmeFeedController::MY_CONSTANT.
Note that is a ruby issue and has nothing to do with RSpec. When you face an issue like this, I recommend you figure out how to do it with plain ruby (e.g. in IRB) before worrying about how it works in RSpec (usually it will be the same, anyway).
If you want to know how constants work in ruby, I commend you watch this talk that explains them in detail.
Also, you can do this without repeating controller class name spaces.
describe AcmeFeedController do
if "tests something" do
c = controller.class.const_get('MY_CONSTANT')
end
end
This kind of trick may not be approved in application codes, but in tests it may be.

after_request in capybara (in rspec)?

We want to validate after every request that there is no escaped HTML or XSS on the page. In Cucumber, we have an AfterStep that does this (as long as the current page is not the same as the previous page).
Is there any way to do this?
EDIT: See https://gist.github.com/603295 for an example of an old (no longer working) version of what I'm hoping to find.
Rspec gives you these after hooks:
https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/hooks/before-and-after-hooks
In short:
after(:each)
after(:all)
after(:suite)
This is the best I could come up with, which was pretty close (however, it's still only after every example, not after every request):
In my spec helper, I added something like this:
RSpec.configure do |config|
config.after(:each, type: :feature) do
# this is run after each example in Capybara, and I did stuff like
page.body.should_not match(UNESCAPED_REGEX)
end
end

Resources