strictly_fake 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +10 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +65 -0
- data/LICENSE.txt +21 -0
- data/README.md +86 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/lib/strictly_fake.rb +132 -0
- data/lib/strictly_fake/version.rb +5 -0
- data/strictly_fake.gemspec +28 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 12c67d1493c17fbe736895b8fa5bf60bd96959e16290a8e38b35a82fc0c21013
|
4
|
+
data.tar.gz: '0088588b7626d8aeee1f80f0171f828aca5e8c558a28538a2321ba9bc7ca1d41'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 347ae7e5cdec81ab141f47c98b93b59cd272344bcc5ef7246468bab280977d215e050ccf8815de613992457f97ba15d1ddc9a82ca4a0f2d796daac290a6003cc
|
7
|
+
data.tar.gz: 7febf5c8242d3144924297de781d1e2cc6fff1ea31cd873f42b0029e90343233668726a06a418daf3d8d98367306821370545d7fd2fc79aa7f851b866d9fb38a
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
strict_fake (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.1)
|
10
|
+
awesome_print (1.8.0)
|
11
|
+
diff-lcs (1.4.4)
|
12
|
+
docile (1.3.2)
|
13
|
+
minitest (5.14.2)
|
14
|
+
parallel (1.19.2)
|
15
|
+
parser (2.7.2.0)
|
16
|
+
ast (~> 2.4.1)
|
17
|
+
rainbow (3.0.0)
|
18
|
+
rake (13.0.1)
|
19
|
+
regexp_parser (1.8.2)
|
20
|
+
rexml (3.2.4)
|
21
|
+
rspec (3.10.0)
|
22
|
+
rspec-core (~> 3.10.0)
|
23
|
+
rspec-expectations (~> 3.10.0)
|
24
|
+
rspec-mocks (~> 3.10.0)
|
25
|
+
rspec-core (3.10.0)
|
26
|
+
rspec-support (~> 3.10.0)
|
27
|
+
rspec-expectations (3.10.0)
|
28
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
29
|
+
rspec-support (~> 3.10.0)
|
30
|
+
rspec-mocks (3.10.0)
|
31
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
+
rspec-support (~> 3.10.0)
|
33
|
+
rspec-support (3.10.0)
|
34
|
+
rubocop (1.1.0)
|
35
|
+
parallel (~> 1.10)
|
36
|
+
parser (>= 2.7.1.5)
|
37
|
+
rainbow (>= 2.2.2, < 4.0)
|
38
|
+
regexp_parser (>= 1.8)
|
39
|
+
rexml
|
40
|
+
rubocop-ast (>= 1.0.1)
|
41
|
+
ruby-progressbar (~> 1.7)
|
42
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
43
|
+
rubocop-ast (1.1.0)
|
44
|
+
parser (>= 2.7.1.5)
|
45
|
+
ruby-progressbar (1.10.1)
|
46
|
+
simplecov (0.19.1)
|
47
|
+
docile (~> 1.1)
|
48
|
+
simplecov-html (~> 0.11)
|
49
|
+
simplecov-html (0.12.3)
|
50
|
+
unicode-display_width (1.7.0)
|
51
|
+
|
52
|
+
PLATFORMS
|
53
|
+
ruby
|
54
|
+
|
55
|
+
DEPENDENCIES
|
56
|
+
awesome_print
|
57
|
+
minitest
|
58
|
+
rake
|
59
|
+
rspec
|
60
|
+
rubocop
|
61
|
+
simplecov
|
62
|
+
strict_fake!
|
63
|
+
|
64
|
+
BUNDLED WITH
|
65
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Featurist
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# StrictlyFake [![Build Status](https://travis-ci.org/featurist/strictly_fake.svg?branch=master)](https://travis-ci.org/featurist/strictly_fake)
|
2
|
+
|
3
|
+
Sometimes fakes are a good choice. But the price is high. In particular, they make changing code harder. You rename a method, but all tests that stub the previous version _keep_ passing. It would be nice if those started to fail so that when they're green again, you can be certain that everything that had to be updated was updated. Well, now you can.
|
4
|
+
|
5
|
+
To be fair, you already can in Rspec with their [verifying double](https://relishapp.com/rspec/rspec-mocks/v/3-9/docs/verifying-doubles). But what about Minitest? And there are still differences. Unlike verifying double:
|
6
|
+
|
7
|
+
- here you need to supply real objects to create fakes. That's controversial - as it goes against the idea of testing in isolation - but realistically, at least in Rails, everything is loaded anyway, so that's a moot point;
|
8
|
+
- strictly_fake is aware of autogenerated methods (e.g. ActiveRecord model accessors);
|
9
|
+
- strictly_fake does not stub constants (e.g. classes). You can, of course, create a fake of a class, but it'll need to be explicitely "injected" into the consumer code;
|
10
|
+
- strictly_fake performs a full parameter check, comparing required, optional and keyword arguments (veryfing double only checks arity).
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'strictly_fake'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle install
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'strictly_fake'
|
28
|
+
|
29
|
+
# Given we don't want to actually pay in tests
|
30
|
+
class PaymentGateway
|
31
|
+
def pay(recipient, amount, reference = nil);
|
32
|
+
# calls remote payment provider...
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# We can use a fake instead
|
37
|
+
payment_gateway = StrictlyFake.new(PaymentGateway.new)
|
38
|
+
|
39
|
+
# Let's stub a method that isn't defined in PaymentGateway:
|
40
|
+
payment_gateway.stub(:bar)
|
41
|
+
# => throws "Can't stub non-existent method PaymentGateway#bar (StrictlyFake::Error)"
|
42
|
+
|
43
|
+
# Let's stub an existing method, but with a wrong signature:
|
44
|
+
payment_gateway.stub(:pay) { |amount| }
|
45
|
+
# => throws "Expected PaymentGateway#pay stub to accept (req, req, opt=), but was (req) (StrictlyFake::Error)"
|
46
|
+
|
47
|
+
# All good now!
|
48
|
+
payment_gateway.stub(:pay) { |recipient, amount, reference = nil| 'Success!' }
|
49
|
+
|
50
|
+
payment_gateway.pay('Dave', 10)
|
51
|
+
# => 'Success!'
|
52
|
+
|
53
|
+
# Makeshift mock
|
54
|
+
invoked = false
|
55
|
+
payment_gateway.stub(:pay) do |recipient, amount, reference = nil|
|
56
|
+
# Further arguments check
|
57
|
+
assert(amount.is_a?(Money))
|
58
|
+
invoked = true
|
59
|
+
end
|
60
|
+
|
61
|
+
payment_gateway.pay('Dave', Money.new(10))
|
62
|
+
assert(invoked)
|
63
|
+
|
64
|
+
# Stubbing class methods is no different
|
65
|
+
time = StrictlyFake.new(Time)
|
66
|
+
|
67
|
+
time.stub(:now) { 'XYZ' }
|
68
|
+
time.now
|
69
|
+
# => 'XYZ'
|
70
|
+
```
|
71
|
+
|
72
|
+
Note: Minitest is required for `assert*` to work.
|
73
|
+
|
74
|
+
## Development
|
75
|
+
|
76
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
77
|
+
|
78
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
79
|
+
|
80
|
+
## Contributing
|
81
|
+
|
82
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/featurist/strictly_fake.
|
83
|
+
|
84
|
+
## License
|
85
|
+
|
86
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'strictly_fake'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './strictly_fake/version'
|
4
|
+
|
5
|
+
# StrictlyFake - verifying fake
|
6
|
+
class StrictlyFake
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
# Actual fake
|
10
|
+
class Fake
|
11
|
+
begin
|
12
|
+
require 'minitest'
|
13
|
+
include Minitest::Assertions
|
14
|
+
rescue LoadError
|
15
|
+
# wtf rubocop
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :assertions
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@assertions = 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(real)
|
26
|
+
@real = real
|
27
|
+
@fake = Fake.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def stub(meth, &block)
|
31
|
+
raise Error, "Can't stub #stub" if meth.to_s == 'stub'
|
32
|
+
|
33
|
+
assert_method_defined(meth)
|
34
|
+
|
35
|
+
expected_parameters = @real.method(meth).parameters
|
36
|
+
actual_parameters = convert_to_lambda(&block).parameters
|
37
|
+
|
38
|
+
assert_method_signature_match(meth, expected_parameters, actual_parameters)
|
39
|
+
|
40
|
+
stub_method(meth, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
# rubocop:disable Lint/MissingSuper
|
44
|
+
def method_missing(meth, *args)
|
45
|
+
@fake.send(meth, *args)
|
46
|
+
end
|
47
|
+
# rubocop:enable Lint/MissingSuper
|
48
|
+
|
49
|
+
def respond_to_missing?(meth, *_args)
|
50
|
+
@fake.respond_to?(meth) || super
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def stub_method(meth, &block)
|
56
|
+
if respond_to?(meth)
|
57
|
+
(class << self; self; end).class_eval do
|
58
|
+
undef_method meth
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
(class << @fake; self; end).class_eval do
|
63
|
+
define_method(meth, &block)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def real_class_name
|
68
|
+
@real.is_a?(Class) ? @real.name : @real.class.name
|
69
|
+
end
|
70
|
+
|
71
|
+
def assert_method_defined(meth)
|
72
|
+
return if @real.respond_to?(meth)
|
73
|
+
|
74
|
+
method_type = @real.is_a?(Class) ? '.' : '#'
|
75
|
+
raise Error, "Can't stub non-existent method #{real_class_name}#{method_type}#{meth}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def assert_method_signature_match(meth, expected_parameters, actual_parameters)
|
79
|
+
return if method_signatures_match?(expected_parameters, actual_parameters)
|
80
|
+
|
81
|
+
method_type = @real.is_a?(Class) ? '.' : '#'
|
82
|
+
|
83
|
+
raise Error, "Expected #{real_class_name}#{method_type}#{meth} stub to "\
|
84
|
+
"accept (#{format_parametes(expected_parameters)}), but was (#{format_parametes(actual_parameters)})"
|
85
|
+
end
|
86
|
+
|
87
|
+
def format_parametes(parameters)
|
88
|
+
parameters.map do |(type, name)|
|
89
|
+
{
|
90
|
+
req: 'req',
|
91
|
+
opt: 'opt=',
|
92
|
+
rest: '*rest',
|
93
|
+
key: ":#{name}",
|
94
|
+
keyreq: ":#{name}",
|
95
|
+
keyrest: '**keyrest'
|
96
|
+
}.fetch(type)
|
97
|
+
end.join(', ')
|
98
|
+
end
|
99
|
+
|
100
|
+
def method_signatures_match?(expected_parameters, actual_parameters)
|
101
|
+
expected_keyword_parameters, expected_positional_parameters = split_parameters_by_type(expected_parameters)
|
102
|
+
actual_keyword_parameters, actual_positional_parameters = split_parameters_by_type(actual_parameters)
|
103
|
+
|
104
|
+
positional_arguments_match = expected_positional_parameters == actual_positional_parameters
|
105
|
+
keyword_arguments_match = expected_keyword_parameters == actual_keyword_parameters
|
106
|
+
|
107
|
+
positional_arguments_match && keyword_arguments_match
|
108
|
+
end
|
109
|
+
|
110
|
+
def split_parameters_by_type(parameters)
|
111
|
+
keyword_parameters, positional_parameters = parameters.partition { |(arg_type)| arg_type.to_s =~ /^key/ }
|
112
|
+
keyrest = pop_keyrest(keyword_parameters)
|
113
|
+
|
114
|
+
[
|
115
|
+
keyword_parameters.map(&:last).sort + (keyrest ? [:keyrest] : []),
|
116
|
+
positional_parameters.map(&:first).reject { |p| p == :block }
|
117
|
+
]
|
118
|
+
end
|
119
|
+
|
120
|
+
def pop_keyrest(keyword_parameters)
|
121
|
+
return unless keyword_parameters.last && keyword_parameters.last.first == :keyrest
|
122
|
+
|
123
|
+
keyword_parameters.pop
|
124
|
+
end
|
125
|
+
|
126
|
+
# Procs don't have `req` parameters, but lambdas do
|
127
|
+
def convert_to_lambda(&block)
|
128
|
+
obj = Object.new
|
129
|
+
obj.define_singleton_method(:_, &block)
|
130
|
+
obj.method(:_).to_proc
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/strictly_fake/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'strictly_fake'
|
7
|
+
spec.version = StrictlyFake::VERSION
|
8
|
+
spec.authors = ['artemave']
|
9
|
+
spec.email = ['artemave@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Stub that automatically verifies that stubbed methods exist and the signatures match the original.'
|
12
|
+
spec.description = "This is similar to Rspec's Veryfing Double, but standalone. So can be used in Minitest."
|
13
|
+
spec.homepage = 'https://github.com/featurist/strictly_fake'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
spec.bindir = 'exe'
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ['lib']
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: strictly_fake
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- artemave
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-24 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This is similar to Rspec's Veryfing Double, but standalone. So can be
|
14
|
+
used in Minitest.
|
15
|
+
email:
|
16
|
+
- artemave@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- ".gitignore"
|
22
|
+
- ".travis.yml"
|
23
|
+
- Gemfile
|
24
|
+
- Gemfile.lock
|
25
|
+
- LICENSE.txt
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- bin/console
|
29
|
+
- lib/strictly_fake.rb
|
30
|
+
- lib/strictly_fake/version.rb
|
31
|
+
- strictly_fake.gemspec
|
32
|
+
homepage: https://github.com/featurist/strictly_fake
|
33
|
+
licenses:
|
34
|
+
- MIT
|
35
|
+
metadata:
|
36
|
+
homepage_uri: https://github.com/featurist/strictly_fake
|
37
|
+
source_code_uri: https://github.com/featurist/strictly_fake
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.4.0
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubygems_version: 3.0.3
|
54
|
+
signing_key:
|
55
|
+
specification_version: 4
|
56
|
+
summary: Stub that automatically verifies that stubbed methods exist and the signatures
|
57
|
+
match the original.
|
58
|
+
test_files: []
|