def create_csv(some_hash)
CSV.open("public/generated_file.csv", "wb") do |row|
csv << ['Time', Time.now]
some_hash.to_a.each { |data| row << data }
end
end
How do I test that this method actually creates a file so I could check an output?
Here is a link to the rspec docs. It uses to_be_an_existing_file to check for presence.
require 'spec_helper'
RSpec.describe 'Check if file exists and is file', :type => :aruba do
let(:file) { 'public/generated_file.csv' }
before { create_csv({...}) }
after { FileUtils.rm_rf(Dir["#{Rails.root}/public/generated_file.csv"]) }
it { expect(file).to be_an_existing_file }
end
Related
I have the following controller concern that is used for authentication:
module ValidateEventRequest
extend ActiveSupport::Concern
def event_request_verified?(request)
sha256 = OpenSSL::Digest::SHA256.new
secret = app_client_id
body = request.body.read
signature = OpenSSL::HMAC.hexdigest(sha256, secret, body)
([signature] & [request.headers['X-Webhook-Signature'], request.headers['X-Api-Signature']]).present?
end
private
def app_client_id
ENV['APP_CLIENT_ID']
end
end
So far I have the following Rspec Test setup to hit this:
RSpec.describe ValidateEventRequest, type: :concern do
let!(:current_secret) { SecureRandom.hex }
describe '#event_request_verified?' do
it 'validates X-Webhook-Signature' do
# TBD
end
it 'validates X-Api-Signature' do
# TBD
end
end
end
I started out with stubbing the request, then mocking and stubbing, and now I am down to scrapping what I have and seeking assistance. 100% coverage is important to me and I am looking for some pointers on how to structure tests that cover this 100%.
object_double is handy for testing concerns:
require 'rails_helper'
describe MyClass do
subject { object_double(Class.new).tap {|c| c.extend MyClass} }
it "extends the subject" do
expect(subject.respond_to?(:some_method_in_my_class)).to be true
# ...
Then you can test subject like any other class. Of course you need to pass in the appropriate arguments when testing methods, which may mean creating additional mocks -- in your case a request object.
Here is how I solved this issue, and I am open to ideas:
RSpec.describe ValidateApiRequest, type: :concern do
let!(:auth_secret) { ENV['APP_CLIENT_ID'] }
let!(:auth_sha256) { OpenSSL::Digest::SHA256.new }
let!(:auth_body) { 'TESTME' }
let(:object) { FakeController.new }
before(:each) do
allow(described_class).to receive(:secret).and_return(auth_secret)
class FakeController < ApplicationController
include ValidateApiRequest
end
end
after(:each) do
Object.send :remove_const, :FakeController
end
describe '#event_request_verified?' do
context 'X-Api-Signature' do
it 'pass' do
request = OpenStruct.new(headers: { 'X-Api-Signature' => OpenSSL::HMAC.hexdigest(auth_sha256, auth_secret, auth_body) }, raw_post: auth_body)
expect(object.event_request_verified?(request)).to be_truthy
end
it 'fail' do
request = OpenStruct.new(headers: { 'X-Api-Signature' => OpenSSL::HMAC.hexdigest(auth_sha256, 'not-the-same', auth_body) }, raw_post: auth_body)
expect(object.event_request_verified?(request)).to be_falsey
end
end
context 'X-Webhook-Signature' do
it 'pass' do
request = OpenStruct.new(headers: { 'X-Webhook-Signature' => OpenSSL::HMAC.hexdigest(auth_sha256, auth_secret, auth_body) }, raw_post: auth_body)
expect(object.event_request_verified?(request)).to be_truthy
end
it 'fail' do
request = OpenStruct.new(headers: { 'X-Webhook-Signature' => OpenSSL::HMAC.hexdigest(auth_sha256, 'not-the-same', auth_body) }, raw_post: auth_body)
expect(object.event_request_verified?(request)).to be_falsey
end
end
end
end
I'm trying to test the action to import products from a CSV.
I'm being able to get the first test to pass. The params[:file] goes to the controller as a string "#<StringIO:0x007fc0d40a0bd0>" which doesn't make my test fail but it isn't the correct behaviour.
The second test I'm getting the following error
private method `gets' called for #<ActionDispatch::Http::UploadedFile:0x007fd391de0a00>
Here's is my spec (content is the CSV content)
# spec/controllers/products_controller_spec.rb
describe 'POST import with file' do
before do
post :import, file: file
end
context 'with invalid data' do
subject { Spree::Product.count }
let(:file) { StringIO.new("") }
it { is_expected.to eq 0 }
end
context 'with valid data' do
subject { Spree::Product.count }
let(:file) { ActionDispatch::Http::UploadedFile.new(params) }
let(:params) do
{
original_filename: 'file.csv',
content_type: 'text/csv',
tempfile: StringIO.new(content)
}
end
it { is_expected.to eq 1 }
end
end
Try to use Rack::Test::UploadedFile to upload a test file, something like this
context 'with valid data' do
subject { Spree::Product.count }
let(:path_to_file) { Rails.root.join 'spec/fixtures/file.csv' }
let(:file) { Rack::Test::UploadedFile.new path_to_file, 'text/csv' }
it { is_expected.to eq 1 }
end
I'm trying to test the following code:
module ApplicationHelper
def current_book
Book.find(params[:id])
end
end
using the following test with RSpec:
RSpec.describe ApplicationHelper, :type => :helper do
describe "#current_book" do
book_1 = create(:book)
params = {}
params[:id] = book_1.id
expect(helper.current_book).to eq(book_1)
end
end
But for some reason the params[:id] parameter isn't being passed in properly. Any suggestions with this?
You need to stub the params:
RSpec.describe ApplicationHelper, type: :helper do
describe "#current_book" do
let(:first_book) { create(:book) }
before(:all) { helper.stub!(:params).and_return(id: 1) }
it "returns a book with a matching id" do
expect(helper.current_book).to eq(first_book)
end
end
end
Here another way of stubbing params. I think this requires rspec 3 can't remember for sure.
context 'path is a route method' do
before { allow(helper).to receive(:params).and_return(order_by: { updated_at: :desc }) }
subject { helper.sortable_link_to('Created At', order_by: :created_at) }
it { is_expected.to match /comments/ }
it { is_expected.to match /\?order_by/}
it { is_expected.to match /\?order_by%5Bupdated_at%5D=asc/}
end
I have a model which has a to_csv method and import method, trying to test this in rspec to make sure it does the right thing but having a problem. I get the following error:
Failures:
1) Category Class import should create a new record if id does not exist
Failure/Error: Category.import("filename", product)
NoMethodError:
undefined method `path' for "filename":String
Model:
class Category
...<snip>
def self.import(file, product)
product = Product.find(product)
CSV.foreach(file.path, headers: true, col_sep: ";") do |row|
row = row.to_hash
row["variations"] = row["variations"].split(",").map { |s| s.strip }
category = product.categories.find(row["id"]) || Category.new(row)
if category.new_record?
product.categories << category
else
category.update_attributes(row)
end
end
end
def self.to_csv(product, options = {})
product = Product.find(product)
CSV.generate(col_sep: ";") do |csv|
csv << ['id','title','description','variations']
product.categories.each do |category|
variations = category.variations.join(',')
csv << [category.id, category.title, category.description, variations]
end
end
end
end
My Test:
describe Category do
describe 'Class' do
subject { Category }
it { should respond_to(:import) }
it { should respond_to(:to_csv) }
let(:data) { "id;title;description;variations\r1;a title;;abd" }
describe 'import' do
it "should create a new record if id does not exist" do
product = create(:product)
File.stub(:open).with("filename","rb") { StringIO.new(data) }
Category.import("filename", product)
end
end
end
end
I would just make Category.import take a filename:
Category.import("filename", product)
Then Category.import just passes this filename to the CSV.foreach call:
CSV.foreach(filename, headers: true, col_sep: ";") do |row|
It's then not necessary to stub File.open or any of that jazz.
I'm using ruby 1.9 and I'm trying to do BDD. My first test 'should read in the csv' works, but the second where I require a file object to be mocked doesn't.
Here is my model spec:
require 'spec_helper'
describe Person do
describe "Importing data" do
let(:person) { Person.new }
let(:data) { "title\tsurname\tfirstname\t\rtitle2\tsurname2\tfirstname2\t\r"}
let(:result) {[["title","surname","firstname"],["title2","surname2","firstname2"]] }
it "should read in the csv" do
CSV.should_receive(:read).
with("filename", :row_sep => "\r", :col_sep => "\t")
person.import("filename")
end
it "should have result" do
filename = mock(File, :exists? => true, :read => data)
person.import(filename).should eq(result)
end
end
end
Here is the code so far:
class Person < ActiveRecord::Base
attr_accessor :import_file
def import(filename)
CSV.read(filename, :row_sep => "\r", :col_sep => "\t")
end
end
I basically want to mock a file so that when the CSV method tries to read from the file it returns my data variable. Then I can test if it equals my result variable.
You can stub File.open:
let(:data) { "title\tsurname\tfirstname\rtitle2\tsurname2\tfirstname2\r" }
let(:result) {[["title","surname","firstname"],["title2","surname2","firstname2"]] }
it "should parse file contents and return a result" do
expect(File).to receive(:open).with("filename","rb") { StringIO.new(data) }
person.import("filename").should eq(result)
end
StringIO essentially wraps a string and makes it behave like an IO object (a File in this case)
Stubbing File.open breaks pry. To avoid this you can stub CSV.read, which isn't as robust as stubbing file, but will let you use pry:
let(:data) do
StringIO.new <<-CSV_DATA
0;48;78;108;279;351;405;694;872;1696
1.03;9.28;13.4;18.56;29.9;30.93;42.27;77.32;85.57;100.0
0.0;2.94;8.82;11.76;44.12;97.06;100.0;100.0;100.0;100.0
CSV_DATA
end
let(:csv_config) { { headers: true, return_headers: true, converters: :numeric } }
before { allow(CSV).to receive(:read).with(csv_path, csv_config) { CSV.parse(data, csv_config) } }