strictly_fake 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .rspec_status
@@ -0,0 +1,10 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.6.6
6
+ before_install: gem install bundler -v 2.1.4
7
+ install: bundle
8
+ script:
9
+ - bundle exec rubocop
10
+ - COVERAGE=1 bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in strictly_fake.gemspec
6
+ gemspec
7
+
8
+ gem 'awesome_print'
9
+ gem 'minitest'
10
+ gem 'rake'
11
+ gem 'rspec'
12
+ gem 'rubocop'
13
+ gem 'simplecov'
@@ -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
@@ -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.
@@ -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).
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StrictlyFake
4
+ VERSION = '0.1.0'
5
+ 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: []