Skip to content

Commit b042748

Browse files
authored
Merge pull request #15 from RadiusNetworks/using-tempfile
Add `using_tempfile` file stub helper
2 parents 0add694 + df984f5 commit b042748

File tree

5 files changed

+417
-0
lines changed

5 files changed

+417
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Enhancements
66

7+
- Add "temp file" helpers for working with file stubs (Aaron Kromer, #15)
78
- Upgrade to Rubocop 0.59.x (Aaron Kromer, #14)
89
- Adjust common Rubocop configuration (Aaron Kromer, #14)
910
- `Layout/EmptyLineAfterGuardClause` is enabled by default

README.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,135 @@ expect(a).to include(:red).and exclude(:yellow)
590590
expect(a).to exclude(:yellow).and include(:red)
591591
```
592592

593+
### Working with Temp Files
594+
595+
These helpers are meant to ease the creation of temporary files to either stub
596+
the data out or provide a location for data to be saved then verified.
597+
598+
In the case of file stubs, using these helpers allows you to co-locate the file
599+
data with the specs. This makes it easy for someone to read the spec and
600+
understand the test case; instead of having to find a fixture file and look at
601+
its data. This also makes it easy to change the data between specs, allowing
602+
them to focus on just what they need.
603+
604+
#### Usage
605+
606+
There are multiple ways you can use these helpers. Which method you choose
607+
depends on how much perceived magic/syntactic sugar you want:
608+
609+
- Call the helpers directly on the module:
610+
611+
```ruby
612+
require 'radius/spec/tempfile'
613+
614+
def write_hello_world(filepath)
615+
File.write filepath, "Hello World"
616+
end
617+
618+
Radius::Spec::Tempfile.using_tempfile do |pathname|
619+
write_hello_world pathname
620+
File.read(pathname)
621+
# => "Hello World"
622+
end
623+
```
624+
- Include the helper methods explicitly:
625+
626+
```ruby
627+
require 'radius/spec/tempfile'
628+
629+
RSpec.describe AnyClass do
630+
include Radius::Spec::Tempfile
631+
632+
it "includes the file helpers" do
633+
using_tempfile do |pathname|
634+
code_under_test pathname
635+
expect(pathname.read).to eq "Any written data"
636+
end
637+
end
638+
end
639+
```
640+
- Include the helper methods via metadata:
641+
642+
```ruby
643+
RSpec.describe AnyClass do
644+
it "includes the file helpers", :tempfile do
645+
using_tempfile do |pathname|
646+
code_under_test pathname
647+
expect(pathname.read).to eq "Any written data"
648+
end
649+
end
650+
end
651+
```
652+
653+
When using this metadata option you do not need to explicitly require the
654+
tempfile feature. This gem registers metadata with the RSpec configuration
655+
when it loads and `RSpec` is defined. When the metadata is first used it
656+
will automatically require the tempfile feature and include the helpers.
657+
658+
Any of following metadata will include the factory helpers:
659+
660+
- `:tempfile`
661+
- `:tmpfile`
662+
663+
There are a few additional behaviors to note:
664+
665+
- Data can be stubbed by the helper through the `data` keyword arg:
666+
667+
```ruby
668+
stub_data = "Any file stub data text."
669+
Radius::Spec::Tempfile.using_tempfile(data: stub_data) do |stubpath|
670+
File.read(stubpath)
671+
# => "Any file stub data text."
672+
end
673+
```
674+
675+
It can even be inlined using heredocs:
676+
677+
```ruby
678+
Radius::Spec::Tempfile.using_tempfile(data: <<~TEXT) do |stubpath|
679+
Any file stub data text.
680+
TEXT
681+
# Yard formats heredoc args oddly
682+
File.read(stubpath)
683+
# => "Any file stub data text.\n"
684+
end
685+
```
686+
687+
> NOTE: That when inlining like this heredocs add an extra new line. To
688+
> remove it use `.chomp` on the kwarg:
689+
>
690+
> ```ruby
691+
> using_tempfile(data: <<~TEXT.chomp) do |pathname|
692+
> This has no newline.
693+
> TEXT
694+
> # ...
695+
> end
696+
> ```
697+
698+
- Additional arguments and options are forwarded directly to
699+
[Tempfile.create](https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html#method-c-create)
700+
701+
This allows you to set custom file extensions:
702+
703+
```ruby
704+
Radius::Spec::Tempfile.using_tempfile(%w[custom_name .myext]) do |pathname|
705+
pathname.extname
706+
# => ".myext"
707+
end
708+
```
709+
710+
Or change the file encoding:
711+
712+
```ruby
713+
Radius::Spec::Tempfile.using_tempfile(encoding: "ISO-8859-1", data: <<~DATA) do |pathname|
714+
Résumé
715+
DATA
716+
# Yard formats heredoc args oddly
717+
File.read(pathname)
718+
# => "R\xE9sum\xE9\n"
719+
end
720+
```
721+
593722
## Development
594723

595724
After checking out the repo, run `bin/setup` to install dependencies. Then, run

lib/radius/spec/rspec.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@
109109
config.include Radius::Spec::ModelFactory, :model_factory, :model_factories
110110
end
111111

112+
config.when_first_matching_example_defined(:tempfile, :tmpfile) do
113+
require 'radius/spec/tempfile'
114+
config.include Radius::Spec::Tempfile, :tempfile, :tmpfile
115+
end
116+
112117
config.when_first_matching_example_defined(type: :controller) do
113118
require 'radius/spec/model_factory'
114119
config.include Radius::Spec::ModelFactory, type: :controller

lib/radius/spec/tempfile.rb

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# frozen_string_literal: true
2+
3+
require 'pathname'
4+
require 'tempfile'
5+
6+
module Radius
7+
module Spec
8+
# Temporary file helpers
9+
#
10+
# These helpers are meant to ease the creation of temporary files to either
11+
# stub the data out or provide a location for data to be saved then
12+
# verified.
13+
#
14+
# In the case of file stubs, using these helpers allows you to co-locate
15+
# the file data with the specs. This makes it easy for someone to read the
16+
# spec and understand the test case; instead of having to find a fixture
17+
# file and look at its data. This also makes it easy to change the data
18+
# between specs, allowing them to focus on just what they need.
19+
#
20+
# To make these helpers available require them after the gem:
21+
#
22+
# ```ruby
23+
# require 'radius/spec'
24+
# require 'radius/spec/tempfile'
25+
# ```
26+
#
27+
# ### Including Helpers in Specs
28+
#
29+
# There are multiple ways you can use these helpers. Which method you
30+
# choose depends on how much perceived magic/syntactic sugar you want:
31+
#
32+
# - call the helpers directly on the module
33+
# - manually include the helper methods in the specs
34+
# - use metadata to auto load this feature and include it in the specs
35+
#
36+
# When using the metadata option you do not need to explicitly require the
37+
# module. This gem registers metadata with the RSpec configuration when it
38+
# loads and `RSpec` is defined. When the matching metadata is first used it
39+
# will automatically require and include the helpers.
40+
#
41+
# Any of following metadata will include the factory helpers:
42+
#
43+
# - `:tempfile`
44+
# - `:tmpfile`
45+
#
46+
# @example use a helper directly in specs
47+
# require 'radius/spec/tempfile'
48+
#
49+
# RSpec.describe AnyClass do
50+
# it "includes the file helpers" do
51+
# Radius::Spec::Tempfile.using_tempfile do |pathname|
52+
# code_under_test pathname
53+
# expect(pathname.read).to eq "Any written data"
54+
# end
55+
# end
56+
# end
57+
# @example manually include the helpers
58+
# require 'radius/spec/tempfile'
59+
#
60+
# RSpec.describe AnyClass do
61+
# include Radius::Spec::Tempfile
62+
# it "includes the file helpers" do
63+
# using_tempfile do |pathname|
64+
# code_under_test pathname
65+
# expect(pathname.read).to eq "Any written data"
66+
# end
67+
# end
68+
# end
69+
# @example use metadata to auto include the helpers
70+
# RSpec.describe AnyClass do
71+
# it "includes the file helpers", :tempfile do
72+
# using_tempfile do |pathname|
73+
# code_under_test pathname
74+
# expect(pathname.read).to eq "Any written data"
75+
# end
76+
# end
77+
# end
78+
# @since 0.5.0
79+
module Tempfile
80+
module_function
81+
82+
# Convenience wrapper for managaing temporary files.
83+
#
84+
# This creates a temporary file and yields its path to the provided
85+
# block. When the block returns the temporary file is deleted.
86+
#
87+
# ### Optional Parameters
88+
#
89+
# The block is required. All other parameters are optional. All
90+
# parameters except `data` are Ruby version dependent and will be
91+
# forwarded directly to the stdlib's
92+
# {https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html#method-c-create
93+
# `Tempfile.create`}. The when the `data` parameter is provided it's
94+
# contents will be written to the temporary file prior to yielding to the
95+
# block.
96+
#
97+
# @example creating a tempfile to pass to code
98+
# def write_hello_world(filepath)
99+
# File.write filepath, "Hello World"
100+
# end
101+
#
102+
# Radius::Spec::Tempfile.using_tempfile do |pathname|
103+
# write_hello_world pathname
104+
# end
105+
# @example creating a file stub
106+
# stub_data = "Any file stub data text."
107+
# Radius::Spec::Tempfile.using_tempfile(data: stub_data) do |stubpath|
108+
# # File.read(stubpath)
109+
# # => "Any file stub data text."
110+
# code_under_test stubpath
111+
# end
112+
# @example creating a file stub inline
113+
# Radius::Spec::Tempfile.using_tempfile(data: <<~TEXT) do |stubpath|
114+
# Any file stub data text.
115+
# TEXT
116+
# # File.read(stubpath)
117+
# # => "Any file stub data text.\n"
118+
# code_under_test stubpath
119+
# end
120+
# @example creating a file stub inline without trailing newline
121+
# Radius::Spec::Tempfile.using_tempfile(data: <<~TEXT.chomp) do |stubpath|
122+
# Any file stub data text.
123+
# TEXT
124+
# # File.read(stubpath)
125+
# # => "Any file stub data text."
126+
# code_under_test stubpath
127+
# end
128+
# @example writing binary data inline
129+
# Radius::Spec::Tempfile.using_tempfile(encoding: Encoding::BINARY, data: <<~BIN.chomp) do |binpath|
130+
# \xC8\x90\xC5\x9D\xE1\xB9\x95\xC4\x93\xC4\x89
131+
# BIN
132+
# # File.read(binpath)
133+
# # => "Ȑŝṕēĉ"
134+
# code_under_test binpath
135+
# end
136+
# @param args [Object] addition file creation options
137+
#
138+
# Passed directly to {https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html#method-c-create
139+
# `Tempfile.create`}; see the stdlib docs for details on available
140+
# options.
141+
# @param data [String] stub data to write to the file before yielding
142+
# @param kwargs [Hash{Symbol => Object}] addition file creation options
143+
#
144+
# Passed directly to {https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html#method-c-create
145+
# `Tempfile.create`}; see the stdlib docs for details on available
146+
# options.
147+
# @yieldparam pathname [Pathname] {https://ruby-doc.org/stdlib/libdoc/pathname/rdoc/Pathname.html path}
148+
# of the created temporary file
149+
# @note A block must be provided
150+
# @see https://ruby-doc.org/stdlib/libdoc/pathname/rdoc/Pathname.html Pathname
151+
# @see https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html Tempfile
152+
def using_tempfile(*args, data: nil, **kwargs)
153+
args << 'tmpfile' if args.empty?
154+
::Tempfile.create(*args, **kwargs) do |f|
155+
f.write(data)
156+
f.close
157+
yield Pathname(f.path)
158+
end
159+
end
160+
end
161+
end
162+
end

0 commit comments

Comments
 (0)