Is it possible to mock File.each_line with a block?
Hello,
I'm using Crystal 0.31.1.
I'm trying to mock a call File.each_line(path), and I wasn't sure if mocks.cr supports this? Here's an example of what I'm trying to do:
class TestClass
@filename : String
@contents : String
def initialize(@filename : String)
@contents = ""
end
def process
@contents = String.build do |io|
File.each_line(@filename) do |line|
io << line
end
end
end
end
And I would like to write a spec like this:
require "./spec_helper"
Spectator.describe TestClass do
it "reads the file lines into memory" do
# Sorry I don't think this line is correct, but I'm not sure how to do it.
file_mock = Mocks.class_double(File)
allow(file_mock).to receive(
self.each_line("example_path")
).and_yield(["hey, world!", "how's it hanging", "this is a line in a file"])
instance = TestClass.new("example_path")
instance.process
expect(instance.contents[0]).to eq("hey, world!")
end
end
Can this be achieved with mocks.cr, or should I be doing something else?
It would also be great if you could add some usage examples for mocking File.read and File.each_line. Thanks!
Oh cool, this example spec was really helpful: https://github.com/waterlink/mocks.cr/blob/master/spec/mocks/create_module_mock_spec.cr
So I figured out that I can do this in the meantime:
# src/test_class.cr
class TestClass
getter :contents
@filename : String
@contents : String
def initialize(@filename : String)
@contents = ""
end
def process
@contents = String.build do |io|
lines = File.read_lines(@filename)
lines.each do |line|
io << line
end
end
end
end
# spec_helper.cr
require "../src/*"
require "spec"
require "spectator"
require "mocks/spec"
# spec/test_class_spec.cr
require "./spec_helper"
Mocks.create_mock File do
mock self.read_lines(filename, encoding = nil, invalid = nil, chomp = true)
end
Spectator.describe TestClass do
it "reads the file lines into memory" do
allow(File).to receive(self.read_lines("example_file")).and_return(
["hey, world!", "how's it hanging"])
instance = TestClass.new("example_path")
instance.process
expect(instance.contents[0]).to eq("hey, world!")
end
end
So I'm making progress! But unfortunately this crashes with an "Arithmetic overflow" in lib/mocks/src/mocks/registry.cr
$ crystal spec
E
Failures:
1) TestClass reads the file lines into memory
Error: Arithmetic overflow
OverflowError: Arithmetic overflow
lib/mocks/src/mocks/registry.cr:86:24 in 'hash'
/usr/local/Cellar/crystal/0.31.1/src/hash.cr:893:12 in 'key_hash'
/usr/local/Cellar/crystal/0.31.1/src/hash.cr:336:5 in 'upsert'
/usr/local/Cellar/crystal/0.31.1/src/hash.cr:912:5 in '[]='
lib/mocks/src/mocks/registry.cr:103:9 in 'add'
lib/mocks/src/mocks/registry.cr:155:9 in 'store_stub'
lib/mocks/src/mocks/allow.cr:16:7 in 'to'
spec/process_comments_spec.cr:33:16 in '__temp_46'
spec/process_comments_spec.cr:30:3 in 'run_instance'
lib/spectator/src/spectator/runnable_example.cr:74:11 in 'run_example'
lib/spectator/src/spectator/runnable_example.cr:64:9 in '->'
lib/spectator/src/spectator/runnable_example.cr:255:3 in 'run_wrapper'
lib/spectator/src/spectator/runnable_example.cr:44:9 in 'capture_result'
lib/spectator/src/spectator/runnable_example.cr:12:7 in 'run_impl'
lib/spectator/src/spectator/example.cr:30:7 in 'run'
lib/spectator/src/spectator/internals/harness.cr:29:7 in 'run'
lib/spectator/src/spectator/runner.cr:60:18 in 'run_example'
lib/spectator/src/spectator/runner.cr:35:9 in 'collect_results'
lib/spectator/src/spectator/runner.cr:356:5 in 'run'
lib/spectator/src/spectator.cr:113:5 in 'run'
lib/spectator/src/spectator.cr:87:29 in '->'
/usr/local/Cellar/crystal/0.31.1/src/kernel.cr:255:3 in 'run'
/usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:47:5 in 'main'
/usr/local/Cellar/crystal/0.31.1/src/crystal/main.cr:106:3 in 'main'
# spec/process_comments_spec.cr:30
Finished in 67 microseconds
1 examples, 1 failures, 1 errors, 0 pending
Failed examples:
crystal spec spec/process_comments_spec.cr:30 # TestClass reads the file lines into memory
Finished in 76.04 milliseconds
0 examples, 0 failures, 0 errors, 0 pending
UPDATE:
I added this code to the crashing method:
def hash
puts "object_id.hash: #{object_id.hash}"
puts "args.hash: #{args.hash}"
puts "object_id.hash * 32: #{object_id.hash * 32}"
object_id.hash * 32 + args.hash
end
Output:
object_id.hash: 7261337864297883403
args.hash: 6437199635058613238
(Crashes on object_id.hash * 32.)
UPDATE 2
I sent a PR to prevent this overflow crash with some bit shifts, but not sure if that's the best solution.