rspec test failing - methods.include? - ruby

I keep getting this validation error in rspec. Could someone please tell what I'm doing wrong?
1) MyServer uses module
Failure/Error: expect(MyClient.methods.include?(:connect)).to be true
expected true
got false
# ./spec/myclient_spec.rb:13:in `block (2 levels) in <top (required)>'
This is my client.rb
#!/bin/ruby
require 'socket'
# Simple reuseable socket client
module SocketClient
def connect(host, port)
sock = TCPSocket.new(host, port)
begin
result = yield sock
ensure
sock.close
end
result
rescue Errno::ECONNREFUSED
end
end
# Should be able to connect to MyServer
class MyClient
include SocketClient
end
And this is my spec.rb
describe 'My server' do
subject { MyClient.new('localhost', port) }
let(:port) { 1096 }
it 'uses module' do
expect(MyClient.const_defined?(:SocketClient)).to be true
expect(MyClient.methods.include?(:connect)).to be true
end
I have method connect defined in module SocketClient. I don't understand why the test keeps failing.

The class MyClient doesn't have a method named connect. Try it: MyClient.connect will not work.
If you want to check what methods a class defines for its instances, use instance_methods: MyClient.instance_methods.include?(:connect) will be true. methods lists the methods an object itself responds to, so MyClient.new(*args).methods.include?(:connect) would be true.
Really, though, for checking whether a specific instance method exists on a class you should use method_defined?, and for checking whether an object itself responds to a specific method, you should use respond_to?:
MyClient.method_defined?(:connect)
MyClient.new(*args).respond_to?(:connect)
If you really do want MyClient.connect to work directly, you'd need to use Object#extend rather than Module#include (see What is the difference between include and extend in Ruby?).

Related

How to test a Ruby Roda app using RSpec to pass an argument to app.new with initialize

This question probably has a simple answer but I can't find any examples for using Roda with RSpec3, so it is difficult to troubleshoot.
I am using Marston and Dees "Effective Testing w/ RSpec3" book which uses Sinatra instead of Roda. I am having difficulty passing an object to API.new, and, from the book, this is what works with Sinatra but fails with a "wrong number of arguments" error when I substitute Roda.
Depending on whether I pass arguments with super or no arguments with super(), the error switches to indicate that the failure occurs either at the initialize method or in the call to Rack::Test::Methods post in the spec.
I see that in Rack::Test, in the Github repo README, I may have to use Rack::Builder.parse_file("config.ru") but that didn't help.
Here are the two errors that rspec shows when using super without brackets:
Failures:
1) MbrTrak::API POST /users when the user is successfully recorded returns the user id
Failure/Error: post '/users', JSON.generate(user)
ArgumentError:
wrong number of arguments (given 1, expected 0)
# ./spec/unit/app/api_spec.rb:21:in `block (4 levels) in <module:MbrTrak>'
And when using super():
1) MbrTrak::API POST /users when the user is successfully recorded returns the user id
Failure/Error: super()
ArgumentError:
wrong number of arguments (given 0, expected 1)
# ./app/api.rb:8:in `initialize'
# ./spec/unit/app/api_spec.rb:10:in `new'
# ./spec/unit/app/api_spec.rb:10:in `app'
# ./spec/unit/app/api_spec.rb:21:in `block (4 levels) in <module:MbrTrak>'
This is my api_spec.rb:
require_relative '../../../app/api'
require 'rack/test'
module MbrTrak
RecordResult = Struct.new(:success?, :expense_id, :error_message)
RSpec.describe API do
include Rack::Test::Methods
def app
API.new(directory: directory)
end
let(:directory) { instance_double('MbrTrak::Directory')}
describe 'POST /users' do
context 'when the user is successfully recorded' do
it 'returns the user id' do
user = { 'some' => 'user' }
allow(directory).to receive(:record)
.with(user)
.and_return(RecordResult.new(true, 417, nil))
post '/users', JSON.generate(user)
parsed = JSON.parse(last_response.body)
expect(parsed).to include('user_id' => 417)
end
end
end
end
end
And here is my api.rb file:
require 'roda'
require 'json'
module MbrTrak
class API < Roda
def initialize(directory: Directory.new)
#directory = directory
super()
end
plugin :render, escape: true
plugin :json
route do |r|
r.on "users" do
r.is Integer do |id|
r.get do
JSON.generate([])
end
end
r.post do
user = JSON.parse(request.body.read)
result = #directory.record(user)
JSON.generate('user_id' => result.user_id)
end
end
end
end
end
My config.ru is:
require "./app/api"
run MbrTrak::API
Well roda has defined initialize method that receives env as an argument which is being called by the app method of the class. Looks atm like this
def self.app
...
lambda{|env| new(env)._roda_handle_main_route}
...
end
And the constructor of the app looks like this
def initialize(env)
When you run your config.ru with run MbrTrack::API you are actually invoking the call method of the roda class which looks like this
def self.call(env)
app.call(env)
end
Because you have redefined the constructor to accept hash positional argument this no longer works and it throws the error you are receiving
ArgumentError:
wrong number of arguments (given 0, expected 1)
Now what problem are you trying to solve, if you want to make your API class configurable one way to go is to try out dry-configurable which is part of the great dry-ruby gem collection.
If you want to do something else feel free to ask.
It has been a long time since you posted your question so hope you will still find this helpful.

Can't understand why I'm getting a `initialize': uninitialized constant Controller::View (NameError)

I'm trying to understand why I'm getting this error and I suspect it's because I have my Controller class and View class in two separate Ruby files. I was told that using require_relative 'filename' should reference all the code from one file into another, but I seem to be missing something. Okay here goes,
In controller.rb file, I have
require_relative 'view'
require_relative 'deck_model'
require_relative 'flashcard_model'
class Controller
def initialize
#deckofcards = Deck.new
#welcome = View.new.welcome
#player_guess = View.new.get_user_guess
#success_view = View.new.success
#failure_view = View.new.failure
end
def run
#Logic to run the game
# #current_card
# #user_guess
puts "Let's see if this prints"
# pull_card_from_deck
end
end
In my view.rb file, I have,
require_relative 'controller'
class View
attr_accessor :userguess
def initialize (userguess = " ")
#userguess = userguess
end
def welcome
system ("clear")
puts "Welcome! Let's play a game."
puts "I'll give you a definition and you have to give me the term"
puts "Ready..."
end
def get_user_guess
#userguess = gets.chomp.downcase
end
def success
puts "Excellent! You got it."
end
def failure
puts "No, that's not quite right."
end
end
However when I run controller.rb, I get the following error,
/Users/sean/Projects/flash/source/controller.rb:11:in `initialize': uninitialized constant Controller::View (NameError)
from /Users/sean/Projects/flash/source/controller.rb:51:in `new'
from /Users/sean/Projects/flash/source/controller.rb:51:in `<top (required)>'
from /Users/sean/Projects/flash/source/view.rb:1:in `require_relative'
from /Users/sean/Projects/flash/source/view.rb:1:in `<top (required)>'
from controller.rb:1:in `require_relative'
from controller.rb:1:in `<main>'
Can anyone please help me figure this out.
You did not post your full code, but it sounds like this is an error caused by the circular dependencies you specified in your project. You have view.rb depending on controller.rb and controller.rb depending on view.rb. The Ruby interpreter will not execute these files simultaneously; it has to execute one and then execute the other.
It looks like it is executing controller.rb first, but it sees that view.rb is required, so it starts executing that. Then in view.rb it sees that controller.rb is required, so it starts executing controller.rb again. Then at some point in controller.rb, you must be creating a new instance of the Controller class. But we aren't done defining the View class yet, so View is undefined and you get an exception while trying to create that controller.
To fix this, you should consider not creating any Controller or View objects until both of the classes are fully loaded.
+1 to #DavidGrayson comment.
If my assumption is correct, your issue is with require_relative 'controller' in your view.rb file.
If you see, it looks like View is requiring Controller then Controller gets loaded which seems to be sending new somewhere to Controller which then sends new to View but it hasn't been completely required.

How do I test this particular method?

I have the following method that is responsible for requesting a URL and returning it's Nokogiri::HTML document. This method checks if a proxy is defined and if it does, it will call OpenURI's open with or without the proxy options.
Implementation
require 'open-uri'
require 'nokogiri'
class MyClass
attr_accessor :proxy
# ....
def self.page_content(url)
if MyClass.proxy
proxy_uri = URI.parse(MyClass.proxy)
Nokogiri::HTML(open(url, :proxy => proxy_uri)) # open provided by OpenURI
else
Nokogiri::HTML(open(url)) # open provided by OpenURI
end
end
end
I have no idea how I should write tests that prove the following:
When a proxy is defined the request OpenURI makes actually uses the proxy info
When a proxy isn't defined, a regular non-proxy connection is made
Here's what I came up with as a start for the tests.
describe MyClass, :vcr do
describe '.proxy' do
it { should respond_to(:proxy) }
end
describe '.page_content' do
let(:url) { "https://google.com/" }
let(:page_content) { subject.page_content(url) }
it 'returns a Nokogiri::HTML::Document' do
page_content.should be_a(Nokogiri::HTML::Document)
end
# How do i test this method actually uses a proxy when it's set vs not set?
context 'when using a proxy' do
# ???
xit 'should set open-uri proxy properties' do
end
end
context 'when not using a proxy' do
# ???
xit 'should not set open-uri proxy properties' do
end
end
end
end
First of all, you need to arrange for the proxy method to return a proxy in one test case and not in the other. If there is a "setter" method for proxy, you can use that, otherwise you can stub the proxy method.
Then, at a minimum, you want to set an expectation on open that it will be called with or without the :proxy option, depending on which test it is. Beyond that, you have the choice of whether to stub and set expectations for the various other calls involved in the method, including URI.parse and Nokogiri::HTML.
See https://github.com/rspec/rspec-mocks for information on establishing your test doubles and setting expectations. Note in particular the and_call_original option if you want to use a partial stubbing approach.
Update: Here's some code to get you started. This works for the non-proxy method. I've left the proxy case for you. Note also that this uses the "partial stubbing" approach, where you still end up calling the external gems.
require 'spec_helper'
describe MyClass do
describe '.proxy' do # NOTE: This test succeeds because of attr_accessor, but you're calling a MyClass.proxy (a class method) within your page_content method
it { should respond_to(:proxy) }
end
describe '.page_content' do
let(:url) { "https://google.com/" }
let(:page_content) { MyClass.page_content(url) } # NOTE: Changed to invoke class method
context 'when not using a proxy' do
before {allow(MyClass).to receive(:proxy).and_return(false)} # Stubbed for no-proxy case
it 'returns a Nokogiri::HTML::Document' do
page_content.should be_a(Nokogiri::HTML::Document)
end
it 'should not set open-uri proxy properties' do
expect(MyClass).to receive(:open).with(url).and_call_original # Stubbing open is tricky, see note afterwards
page_content
end
end
# How do i test this method actually uses a proxy when it's set vs not set?
context 'when using a proxy' do
# ???
xit 'should set open-uri proxy properties' do
end
end
end
end
Stubbing of open is tricky. See How to rspec mock open-uri? for an explanation.

How do I correctly include and test an ActiveSupport::Concern in an RSpec mocked_model?

I'm trying to spec a module by including it in a basic mock_model object. However, when I call the instance method defined in the module ActiveRecord tries to establish a connection with the database.
The module:
module Stuff
module SoftDelete
extend ActiveSupport::Concern
def soft_delete
puts "Called here"
end
end
end
The Spec:
describe Stuff::SoftDelete do
class Network < ActiveRecord::Base
include Stuff::SoftDelete
attr_accessor :deleted_at
end
before (:each) do
#network = mock_model(Network)
end
context "When a record is deleted" do
it "is marked as deleted" do
#network.soft_delete
end
end
end
When I run this Spec, the following error occurs:
1) Stuff::SoftDelete When a record is deleted is marked as deleted
Failure/Error: #network.soft_delete
ActiveRecord::ConnectionNotEstablished:
ActiveRecord::ConnectionNotEstablished
# ./spec/apoc/soft_delete_spec.rb:18:in `block (3 levels) in <top (required)>'
Note: If I include the SoftDelete module in a real ActiveRecord class, it will work. It just seems that mock_model isn't able to deal with the module.
Would love some help on this one.
Thanks!
Do you trust ActiveRecord? If so, don't inherit from it; test your module in isolation. If your module includes calls ActiveRecord methods, stub them and test only your code.

How can I dynamically define Object methods inside Ruby modules?

I have a case where I'm trying to leverage a database configuration file used by Padrino for a custom worker I'm writing without having to load the Padrino environment and without having to modify the existing database configuration. In short, I want to write my code to work with the existing database configuration code rather than modifying the existing configuration.
The database configuration file in question looks like the following:
# This is just here for right now so I can test
# to see if logger is set to anything in DB config
puts "Logger in DB config: #{logger}"
ActiveRecord::Base.configurations[:development] = {
:adapter => 'sqlite3',
:database => Padrino.root('db', "app_development.db") }
ActiveRecord::Base.logger = logger
ActiveRecord::Base.include_root_in_json = true
ActiveRecord::Base.store_full_sti_class = true
ActiveSupport.use_standard_json_time_format = true
ActiveSupport.escape_html_entities_in_json = false
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Padrino.env])
My custom worker looks like the following:
ROOT = File.expand_path(File.dirname(__FILE__))
$LOAD_PATH.unshift(ROOT)
require 'active_record'
require 'logger'
# Mock Padrino module with methods used by database config file
module Padrino
def self.root(*args)
return File.expand_path(File.join("#{ROOT}/..", *args))
end
def self.env
return :development
end
end
module Worker
class << self
attr_accessor :logger
def init
#logger = Logger.new(STDOUT)
#logger.level = Logger::DEBUG
require "#{ROOT}/../config/database"
end
end
end
Worker.init
When I require the database configuration file in my worker I get an error saying the following:
/home/user/devel/app/config/database.rb:3:in `<top (required)>': undefined local variable or method `logger' for main:Object (NameError)
from /home/user/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:55:in `require'
from /home/user/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:55:in `require'
from worker.rb:26:in `init'
from worker.rb:31:in `<main>'
Given this, I modified the Worker#init function to be the following:
def init
#logger = Logger.new(STDOUT)
#logger.level = Logger::DEBUG
Object.send(:define_method, :logger, lambda { #logger })
puts "Logger in Worker module: #{logger}"
require "#{ROOT}/../config/database"
end
This change resulted in the following output:
Logger in Worker module: #<Logger:0x9505088>
Logger in DB config:
I take this to mean the #logger is not in scope in the database configuration file, even though I'm using a lambda when I define the logger method on Object.
Oddly enough, if I pull the Object.send line of code out of the Worker#init function and instead call it right before I call Worker.init like below, I get the following result.
Object.send(:define_method, :logger, lambda { Worker.logger })
Worker.init
results in
Logger in Worker module: #<Logger:0x81bc8b4>
Logger in DB config: #<Logger:0x81bc8b4>
Can someone explain to me why if I make the Object.send call inside the Worker#init function it doesn't work like it does if I make it outside the Worker module?
In your first example, you try to use undefined variable 'logger', as you can see from error message. Try to use the logger feature this way:
ActiveRecord::Base.logger = Logger.new(STDOUT)

Resources