effective_test_bot 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/helpers/effective_test_bot_controller_helper.rb +1 -0
- data/lib/effective_test_bot.rb +61 -0
- data/lib/effective_test_bot/engine.rb +18 -3
- data/lib/effective_test_bot/version.rb +1 -1
- data/lib/generators/templates/test_helper.rb +1 -1
- data/lib/tasks/effective_test_bot_tasks.rake +10 -1
- data/test/concerns/test_botable/base_dsl.rb +142 -0
- data/test/concerns/test_botable/crud_dsl.rb +92 -0
- data/test/concerns/test_botable/member_dsl.rb +41 -0
- data/test/concerns/test_botable/page_dsl.rb +39 -0
- data/test/concerns/test_botable/redirect_dsl.rb +37 -0
- data/test/concerns/test_botable/wizard_dsl.rb +41 -0
- data/test/support/effective_test_bot_assertions.rb +51 -19
- data/test/support/effective_test_bot_form_helper.rb +9 -5
- data/test/support/effective_test_bot_login_helper.rb +3 -3
- data/test/support/effective_test_bot_test_helper.rb +4 -2
- data/test/test_bot/integration/application_test.rb +86 -0
- data/test/test_bot/integration/minitest_test.rb +16 -11
- data/test/test_bot/models/user_test.rb +2 -0
- data/test/test_botable/base_test.rb +62 -0
- data/test/test_botable/crud_test.rb +68 -92
- data/test/test_botable/member_test.rb +20 -0
- data/test/test_botable/page_test.rb +23 -0
- data/test/test_botable/redirect_test.rb +18 -0
- data/test/test_botable/wizard_test.rb +31 -0
- metadata +14 -4
- data/test/concerns/test_botable/crud_test.rb +0 -145
- data/test/test_bot/integration/home_page_test.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: faf3170be69a804110713bd4fffd0d2802841fa2
|
4
|
+
data.tar.gz: 3ac29840a0b524fad45cded4d7d81c3bcb56bee4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1bc8fe65d07f530b193e9498e5d3537944d09903727f0407cc7c93bccfeed52addaa1383a56faea2bc005be2429b1e5d856da74da5bc7209e5949425dfb3d7c
|
7
|
+
data.tar.gz: 4174f5a21a7cfa52081df5d86de979b48cfe1a44fe167a5974b84653cb64f4bcd24555d0787b269336f33540376684bafc566937c46df7186e91b6a6c48c607e
|
data/lib/effective_test_bot.rb
CHANGED
@@ -2,8 +2,69 @@ require "effective_test_bot/engine"
|
|
2
2
|
require "effective_test_bot/version"
|
3
3
|
|
4
4
|
module EffectiveTestBot
|
5
|
+
mattr_accessor :except
|
6
|
+
mattr_accessor :only
|
7
|
+
|
5
8
|
def self.setup
|
6
9
|
yield self
|
7
10
|
end
|
8
11
|
|
12
|
+
def self.skip?(test, assertion = nil)
|
13
|
+
value = [test.to_s.presence, assertion.to_s.presence].compact.join(' ')
|
14
|
+
return false if value.blank?
|
15
|
+
|
16
|
+
if onlies.present?
|
17
|
+
onlies.find { |only| value.start_with?(only) }.blank? # Let partial matches work
|
18
|
+
elsif excepts.present?
|
19
|
+
excepts.find { |except| except == value }.present?
|
20
|
+
else
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.onlies
|
28
|
+
@@onlines ||= begin
|
29
|
+
flatten_and_sort(
|
30
|
+
if ENV['TEST_BOT_TEST'].present?
|
31
|
+
ENV['TEST_BOT_TEST'].to_s.gsub('[', '').gsub(']', '').split(',').map { |str| str.strip }
|
32
|
+
else
|
33
|
+
only
|
34
|
+
end
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.excepts
|
40
|
+
@@excepts ||= flatten_and_sort(except)
|
41
|
+
end
|
42
|
+
|
43
|
+
# config.except = [
|
44
|
+
# 'assert_path',
|
45
|
+
# 'users#show',
|
46
|
+
# 'users#create_invalid' => ['assert_path'],
|
47
|
+
# 'users#create_invalid' => 'assert_unpermitted_params',
|
48
|
+
# 'report_total_allocation_index_path'
|
49
|
+
# ]
|
50
|
+
|
51
|
+
# We need to flatten any Hashes into
|
52
|
+
# 'users#create_invalid' => ['assert_path', 'assert_page_title'],
|
53
|
+
# into this
|
54
|
+
# ['users#create_invalid assert_path'
|
55
|
+
# 'users#create_invalid assert_page_title']
|
56
|
+
|
57
|
+
def self.flatten_and_sort(skips)
|
58
|
+
Array(skips).flat_map do |skip|
|
59
|
+
case skip
|
60
|
+
when Symbol
|
61
|
+
skip.to_s
|
62
|
+
when Hash
|
63
|
+
skip.keys.product(skip.values.flatten).map { |p| p.join(' ') }
|
64
|
+
else
|
65
|
+
skip
|
66
|
+
end
|
67
|
+
end.compact.sort
|
68
|
+
end
|
69
|
+
|
9
70
|
end
|
@@ -14,10 +14,23 @@ module EffectiveTestBot
|
|
14
14
|
|
15
15
|
initializer 'effective_test_bot.test_suite' do |app|
|
16
16
|
Rails.application.config.to_prepare do
|
17
|
+
# test/test_botable/
|
18
|
+
ActionDispatch::IntegrationTest.include BaseTest
|
17
19
|
ActionDispatch::IntegrationTest.include CrudTest
|
18
|
-
ActionDispatch::IntegrationTest.include
|
19
|
-
|
20
|
-
|
20
|
+
ActionDispatch::IntegrationTest.include MemberTest
|
21
|
+
ActionDispatch::IntegrationTest.include PageTest
|
22
|
+
ActionDispatch::IntegrationTest.include RedirectTest
|
23
|
+
ActionDispatch::IntegrationTest.include WizardTest
|
24
|
+
|
25
|
+
# test/concerns/test_botable/
|
26
|
+
ActionDispatch::IntegrationTest.include TestBotable::BaseDsl
|
27
|
+
ActionDispatch::IntegrationTest.include TestBotable::CrudDsl
|
28
|
+
ActionDispatch::IntegrationTest.include TestBotable::MemberDsl
|
29
|
+
ActionDispatch::IntegrationTest.include TestBotable::PageDsl
|
30
|
+
ActionDispatch::IntegrationTest.include TestBotable::RedirectDsl
|
31
|
+
ActionDispatch::IntegrationTest.include TestBotable::WizardDsl
|
32
|
+
|
33
|
+
# test/support/
|
21
34
|
ActionDispatch::IntegrationTest.include EffectiveTestBotAssertions
|
22
35
|
ActionDispatch::IntegrationTest.include EffectiveTestBotFormHelper
|
23
36
|
ActionDispatch::IntegrationTest.include EffectiveTestBotLoginHelper
|
@@ -29,6 +42,8 @@ module EffectiveTestBot
|
|
29
42
|
ActiveSupport.on_load :action_controller do
|
30
43
|
if Rails.env.test?
|
31
44
|
ActionController::Base.send :include, ::EffectiveTestBotControllerHelper
|
45
|
+
|
46
|
+
ActionController::Base.send :before_filter, :expires_now # Prevent 304 Not Modified caching
|
32
47
|
ActionController::Base.send :after_filter, :assign_test_bot_http_headers
|
33
48
|
|
34
49
|
ApplicationController.instance_exec do
|
@@ -1,9 +1,19 @@
|
|
1
1
|
require 'rake/testtask'
|
2
2
|
require 'rails/test_unit/sub_test_task'
|
3
3
|
|
4
|
+
# rake test:bot
|
5
|
+
# rake test:bot TEST=documents#new
|
6
|
+
# rake test:bot TEST=documents#new,documents#show
|
7
|
+
# rake test:bot TEST=documents#new path,documents#show,documents#update_valid no_unpermitted_params
|
8
|
+
|
4
9
|
namespace :test do
|
5
10
|
desc 'Runs Effective Test Bot'
|
6
11
|
task :bot do
|
12
|
+
if ENV['TEST'].present?
|
13
|
+
ENV['TEST_BOT_TEST'] = ENV['TEST']
|
14
|
+
ENV['TEST'] = nil
|
15
|
+
end
|
16
|
+
|
7
17
|
Rake::Task["test:effective_test_bot"].invoke
|
8
18
|
end
|
9
19
|
|
@@ -18,5 +28,4 @@ namespace :test do
|
|
18
28
|
load(seeds) if File.exists?(seeds)
|
19
29
|
end
|
20
30
|
|
21
|
-
|
22
31
|
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module TestBotable
|
2
|
+
module BaseDsl
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
TEST_BOT_TEST_PREFIXES = ['crud_test', 'member_test', 'page_test', 'redirect_test', 'wizard_test']
|
7
|
+
|
8
|
+
# Parses and validates lots of options
|
9
|
+
# This is a big manual merge wherein we translate some DSL methods into one consistent Hash here
|
10
|
+
# The output is what gets sent to each test and defined as lets
|
11
|
+
|
12
|
+
# {
|
13
|
+
# user: User<1> instance
|
14
|
+
# controller_namespace: 'admin'
|
15
|
+
# controller: 'jobs'
|
16
|
+
# skips: []
|
17
|
+
# resource: Post<1> instance
|
18
|
+
# resource_class: Post
|
19
|
+
# resource_name: 'post'
|
20
|
+
# resource_attributes: {:user_id => 3} attributes, if called with an instance of ActiveRecord with non-default attributes
|
21
|
+
# current_test: posts#new (if from the class level) or NIL if from the instance level
|
22
|
+
# }
|
23
|
+
#
|
24
|
+
|
25
|
+
def normalize_test_bot_options!(options)
|
26
|
+
raise 'expected options to be a Hash' unless options.kind_of?(Hash)
|
27
|
+
raise 'expected key :user to be a User' unless options[:user].kind_of?(User)
|
28
|
+
#raise 'expected key :current_test to be a String' unless options[:current_test].kind_of?(String)
|
29
|
+
|
30
|
+
# Controller stuff
|
31
|
+
options[:controller_namespace] ||= options[:namespace]
|
32
|
+
|
33
|
+
# Skip stuff
|
34
|
+
skips = options[:skip] || options[:skips]
|
35
|
+
unless skips.blank? || skips.kind_of?(Symbol) || (skips.kind_of?(Array) && skips.all? { |s| s.kind_of?(Symbol) })
|
36
|
+
raise 'expected skips to be a Symbol or Array of Symbols'
|
37
|
+
end
|
38
|
+
options[:skips] = Array(skips)
|
39
|
+
|
40
|
+
# Resource could be an ActiveRecord Class, Instance of a class, or String
|
41
|
+
# Build a resource stuff
|
42
|
+
if options[:resource].present?
|
43
|
+
obj = options[:resource]
|
44
|
+
raise 'expected resource to be a Class or Instance or String' unless obj.kind_of?(Class) || obj.kind_of?(ActiveRecord::Base) || obj.kind_of?(String)
|
45
|
+
|
46
|
+
if obj.kind_of?(String) # Let's assume this is a controller, 'admin/jobs', or just 'jobs', or 'jobs_controller'
|
47
|
+
obj.sub!('_controller', '')
|
48
|
+
|
49
|
+
(*namespace, klass) = obj.split('/')
|
50
|
+
namespace = Array(namespace).join('/').presence
|
51
|
+
|
52
|
+
# See if I can turn it into a model
|
53
|
+
klass = klass.classify.safe_constantize
|
54
|
+
|
55
|
+
raise "failed to constantize resource from string #{obj}, unable to proceed" unless klass.present?
|
56
|
+
|
57
|
+
options[:controller_namespace] ||= namespace
|
58
|
+
options[:controller] = obj.dup
|
59
|
+
|
60
|
+
obj = klass
|
61
|
+
end
|
62
|
+
|
63
|
+
# Make sure Obj.new() works
|
64
|
+
if obj.kind_of?(Class) && (obj.new() rescue false) == false
|
65
|
+
raise "failed to initialize resource with #{obj}.new(), unable to proceed"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Parse the resource and resource class
|
69
|
+
resource = obj.kind_of?(Class) ? obj.new() : obj
|
70
|
+
resource_class = obj.kind_of?(Class) ? obj : obj.class
|
71
|
+
|
72
|
+
# If obj is an ActiveRecord object with attributes, Post.new(:title => 'My Title')
|
73
|
+
# then compute any explicit attributes, so forms will be filled with those values
|
74
|
+
resource_attributes = if obj.kind_of?(ActiveRecord::Base)
|
75
|
+
empty = resource_class.new()
|
76
|
+
{}.tap { |atts| resource.attributes.each { |k, v| atts[k] = v if empty.attributes[k] != v } }
|
77
|
+
end || {}
|
78
|
+
|
79
|
+
options[:resource] = resource
|
80
|
+
options[:resource_class] = resource_class
|
81
|
+
options[:resource_name] = resource_class.name.underscore
|
82
|
+
options[:resource_attributes] = resource_attributes
|
83
|
+
|
84
|
+
options[:normalized] = true
|
85
|
+
end
|
86
|
+
|
87
|
+
options
|
88
|
+
end
|
89
|
+
|
90
|
+
# Run any test_bot tests first, in the order they're defined
|
91
|
+
# then the rest of the tests with whatever order they come in
|
92
|
+
def runnable_methods
|
93
|
+
public_instance_methods.select do |name|
|
94
|
+
name = name.to_s
|
95
|
+
TEST_BOT_TEST_PREFIXES.any? { |prefix| name.starts_with?(prefix) }
|
96
|
+
end.map(&:to_s) + super
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
# You can't define multiple methods with the same name
|
102
|
+
# So we need to create a unique name, where appropriate, that still looks good in MiniTest output
|
103
|
+
def test_bot_method_name(test_family, current_test)
|
104
|
+
number_of_tests = if current_test.blank?
|
105
|
+
@num_defined_test_bot_tests ||= {}
|
106
|
+
@num_defined_test_bot_tests[test_family] = (@num_defined_test_bot_tests[test_family] || 0) + 1
|
107
|
+
end
|
108
|
+
|
109
|
+
if current_test.present?
|
110
|
+
"#{test_family}: (#{current_test})"
|
111
|
+
elsif number_of_tests > 1
|
112
|
+
"#{test_family}: (#{number_of_tests})"
|
113
|
+
else
|
114
|
+
"#{test_family}:"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Instance Methods
|
120
|
+
|
121
|
+
|
122
|
+
# Using reverse_merge! in the dsl action_tests makes sure that the
|
123
|
+
# class level can assign a current_test variable
|
124
|
+
# wheras the action level ones it's not present.
|
125
|
+
def assign_test_bot_lets!(options)
|
126
|
+
lets = if options.kind_of?(Hash) && options[:normalized]
|
127
|
+
options
|
128
|
+
else
|
129
|
+
self.class.normalize_test_bot_options!(options)
|
130
|
+
end
|
131
|
+
|
132
|
+
lets.each { |k, v| self.class.let(k) { v } } # Using the minitest spec let(:foo) { 'bar' } syntax
|
133
|
+
|
134
|
+
# test_bot may leak some lets from one test to the next, if they're not overridden
|
135
|
+
# I take special care to undefine just current test so far
|
136
|
+
self.class.let(:current_test) { nil } if options[:current_test].blank?
|
137
|
+
|
138
|
+
lets
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# This DSL gives a class level and an instance level way of calling specific test suite
|
2
|
+
#
|
3
|
+
# class PostsTest < ActionDispatch::IntegrationTest
|
4
|
+
# crud_test(Post || 'admin/posts', User.first, except: :show, skip: {create_valid: :path, update_invalid: [:path, :flash]})
|
5
|
+
#
|
6
|
+
# test 'a one-off action' do
|
7
|
+
# crud_action_test(:new, Post, User.first, skip: :title)
|
8
|
+
# end
|
9
|
+
# end
|
10
|
+
|
11
|
+
module TestBotable
|
12
|
+
module CrudDsl
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
CRUD_TESTS = [:new, :create_valid, :create_invalid, :edit, :update_valid, :update_invalid, :index, :show, :destroy]
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
# All this does is define a 'test_bot' method for each required action on this class
|
20
|
+
# So that MiniTest will see the test functions and run them
|
21
|
+
def crud_test(resource, user, options = {})
|
22
|
+
# This skips paramaters is different than the initializer skips, which affect just the rake task
|
23
|
+
|
24
|
+
# These are specificially for the DSL
|
25
|
+
# In the class method, this value is a Hash, in the instance method it's expecting an Array
|
26
|
+
skips = options.delete(:skip) || options.delete(:skips) || {} # So you can skip sub tests
|
27
|
+
raise 'invalid skip syntax, expecting skip: {create_invalid: [:path]}' unless skips.kind_of?(Hash)
|
28
|
+
|
29
|
+
label = options.delete(:label).presence
|
30
|
+
only = options.delete(:only)
|
31
|
+
except = options.delete(:except)
|
32
|
+
|
33
|
+
begin
|
34
|
+
normalize_test_bot_options!(options.merge!(user: user, resource: resource))
|
35
|
+
rescue => e
|
36
|
+
raise "Error: #{e.message}. Expected usage: crud_test(Post || Post.new, User.first, only: [:new, :create], skip: {create_invalid: [:path]})"
|
37
|
+
end
|
38
|
+
|
39
|
+
crud_tests_to_define(only, except).each do |test|
|
40
|
+
options_for_method = options.dup
|
41
|
+
|
42
|
+
options_for_method[:skips] = Array(skips[test]) if skips[test]
|
43
|
+
options_for_method[:current_test] = [
|
44
|
+
options[:controller_namespace].presence,
|
45
|
+
options[:resource_name].pluralize
|
46
|
+
].compact.join('/') + '#' + test.to_s
|
47
|
+
|
48
|
+
method_name = test_bot_method_name('crud_test', label || options_for_method[:current_test])
|
49
|
+
|
50
|
+
define_method(method_name) { crud_action_test(test, resource, user, options_for_method) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Parses the incoming options[:only] and [:except]
|
57
|
+
# To only define the appropriate methods
|
58
|
+
# This guarantees the functions will be defined in the same order as CRUD_TESTS
|
59
|
+
def crud_tests_to_define(only, except)
|
60
|
+
if only
|
61
|
+
only = Array(only).flatten.compact.map { |x| x.to_sym }
|
62
|
+
only = only + [:create_valid, :create_invalid] if only.delete(:create)
|
63
|
+
only = only + [:update_valid, :update_invalid] if only.delete(:update)
|
64
|
+
|
65
|
+
CRUD_TESTS & only
|
66
|
+
elsif except
|
67
|
+
except = Array(except).flatten.compact.map { |x| x.to_sym }
|
68
|
+
except = except + [:create_valid, :create_invalid] if except.delete(:create)
|
69
|
+
except = except + [:update_valid, :update_invalid] if except.delete(:update)
|
70
|
+
|
71
|
+
CRUD_TESTS - except
|
72
|
+
else
|
73
|
+
CRUD_TESTS
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Instance Methods - Call me from within a test
|
79
|
+
#
|
80
|
+
# If obj is a Hash {:resource => ...} just skip over parsing options
|
81
|
+
# And assume it's already been done (by the ClassMethod crud_test)
|
82
|
+
def crud_action_test(test, resource, user = nil, options = {})
|
83
|
+
begin
|
84
|
+
assign_test_bot_lets!(options.reverse_merge!(user: user, resource: resource))
|
85
|
+
rescue => e
|
86
|
+
raise "Error: #{e.message}. Expected usage: crud_action_test(:new, Post || Post.new, User.first, options_hash)"
|
87
|
+
end
|
88
|
+
|
89
|
+
self.send("test_bot_#{test}_test")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# This DSL gives a class level and an instance level way of calling specific test suite
|
2
|
+
#
|
3
|
+
# class PostsTest < ActionDispatch::IntegrationTest
|
4
|
+
# member_test('admin/jobs', 'unarchive', User.first, Post.first)
|
5
|
+
#
|
6
|
+
# test 'a one-off action' do
|
7
|
+
# member_action_test('admin/jobs', 'unarchive', User.first)
|
8
|
+
# end
|
9
|
+
# end
|
10
|
+
|
11
|
+
# A member_test assumes assumes route.name.present? && route.verb.to_s.include?('GET') && route.path.required_names == ['id']
|
12
|
+
# we HAVE TO build or have available one of these resources so we can pass the ID to it and see what happens :)
|
13
|
+
|
14
|
+
module TestBotable
|
15
|
+
module MemberDsl
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
def member_test(controller, action, user, obj_to_param = nil, options = {})
|
21
|
+
options[:current_test] = "#{controller}##{action}"
|
22
|
+
method_name = test_bot_method_name('member_test', options.delete(:label) || options[:current_test])
|
23
|
+
|
24
|
+
define_method(method_name) { member_action_test(controller, action, user, obj_to_param, options) }
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
# Instance Methods - Call me from within a test
|
30
|
+
def member_action_test(controller, action, user, member = nil, options = {})
|
31
|
+
begin
|
32
|
+
assign_test_bot_lets!(options.reverse_merge!(resource: controller, action: action, user: user, member: member))
|
33
|
+
rescue => e
|
34
|
+
raise "Error: #{e.message}. Expected usage: member_test('admin/jobs', 'unarchive', User.first, Post.first || nil)"
|
35
|
+
end
|
36
|
+
|
37
|
+
self.send(:test_bot_member_test)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|