active_cucumber 0.0.3 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b74fd70bd52ca80c98362537b7412860f8da8d04
4
- data.tar.gz: 56ec6ca577595a1d25b51f91eacea3264e48cf15
3
+ metadata.gz: cd6e427a1ea5dd697035f91e1344e7dbfe0f8717
4
+ data.tar.gz: 0998293ca8816de728f5f1b5419e350ac42f712c
5
5
  SHA512:
6
- metadata.gz: f1a7c9599fb559349b1b7b4f2ffc4ecebfe6f3100f10a8c33b872dafa8e8be46a80afdc1efcbbe0683d8938f8404c8caaf9876cc8a4f4738fae9425cc957df4c
7
- data.tar.gz: bd49642ca5cd23485265c6a548345e348f5fcbdcf2cdb5da94625e056d169791e89999340a1d00659407ec75829d2c13772b6038638050d3d0b9c30f593f79d8
6
+ metadata.gz: 0c2ce7cc37de5e7bd600080238d73f8c10f7db99b623db4a51208d7cef6ea66697de28b814830d732df24ea1608f3cb7b15b06e313edf7a158b6c031a4bd5c78
7
+ data.tar.gz: 12bad9a4a815ba38efefed654a9175cf276317d6aea7c9f658a9e7c45db9fe4c9cfea613a6145338ac164f28c4f78b8bd9b44e879ae5a2022bde47e3018f36bc
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_cucumber (0.0.3)
4
+ active_cucumber (0.0.4)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,8 +1,101 @@
1
1
  # ActiveCucumber [![Circle CI](https://circleci.com/gh/Originate/active_cucumber.svg?style=shield)](https://circleci.com/gh/Originate/active_cucumber)
2
2
 
3
- High-level Cucumber helpers for testing
3
+ High-level Cucumber helpers for [creating](#creating-database-records)
4
+ and [verifying](#verifying-database-records)
4
5
  [ActiveRecord](http://guides.rubyonrails.org/active_record_basics.html)-based
5
- database applications using Cucumber tables.
6
+ database entries using Cucumber tables.
7
+
8
+
9
+ ## Creating database records
10
+
11
+ Creating simple fields works out of the box.
12
+ Let's assume we have an application that stores TV shows and their episodes,
13
+ and our tests contain this Cucumber table:
14
+
15
+ ```cucumber
16
+ Given the episodes:
17
+ | NAME | YEAR |
18
+ | Encounter at Farpoint | 1987 |
19
+ | All Good Things | 1994 |
20
+ ```
21
+
22
+ Implementing this step looks like:
23
+
24
+ ```ruby
25
+ Given(/^the episodes:$/) do |table|
26
+ ActiveCucumber.create_many Episode, table
27
+ end
28
+ ```
29
+
30
+ ### Transforming values
31
+
32
+ Let's say our data model also contains a `Series` class
33
+ (an episode belongs to a series, and a series has many episodes).
34
+ We want to also define (and if necessary create) the series that an episode belongs to:
35
+
36
+ ```cucumber
37
+ Given the episodes:
38
+ | SERIES | NAME |
39
+ | Star Trek TNG | Encounter at Farpoint |
40
+ | Star Trek TNG | All Good Things |
41
+ ```
42
+
43
+ ActiveCucumber doesn't require custom step definitions here.
44
+ To make this work, tell ActiveCucumber how to convert particular Cucumber table fields
45
+ into ActiveRecord attributes via a `Creator` class:
46
+
47
+ ```ruby
48
+ class EpisodeCreator < ActiveCucumber::Creator
49
+
50
+ def value_for_series series_name
51
+ Series.find_by(name: series_name) || FactoryGirl.create(:series, name: series_name)
52
+ end
53
+
54
+ end
55
+ ```
56
+
57
+ ActiveCucumber automatically uses creator classes that follow the given naming schema:
58
+ * name is `<class name>Creator`
59
+ * method names are `value_for_<attribute name>`
60
+
61
+
62
+ ### Other columns
63
+
64
+ Cucumber tables can contain columns that provide other test data,
65
+ and don't correspond to attributes on the created object.
66
+
67
+ ```cucumber
68
+ Given the episodes:
69
+ | GENRE | SERIES | NAME |
70
+ | Science Fiction | Star Trek TNG | Encounter at Farpoint |
71
+ | Comedy | The Big Bang Theory | The Big Bran Hypothesis |
72
+ ```
73
+
74
+ A `Genre` has many series, and a series belongs to a genre.
75
+ Episodes are not directly associated with genres.
76
+
77
+ ```ruby
78
+ class EpisodeCreator < ActiveCucumber::Creator
79
+
80
+ def value_for_genre genre_name
81
+ @genre = Genre.find_by(name: genre_name) || FactoryGirl.create(:genre, name: genre_name)
82
+ delete :genre
83
+ end
84
+
85
+ def value_for_show show_name
86
+ Show.find_by(name: show_name) || FactoryGirl.create(:show, name: show_name, genre: @genre)
87
+ end
88
+
89
+ end
90
+ ```
91
+
92
+ Creators decorate the data structure that
93
+ is sent to FactoryGirl to create the record.
94
+ This means `self` inside creator methods behaves like a Hash
95
+ that is pre-populated with the Cucumber table data.
96
+ You can modify this hash, use other field values,
97
+ add or remove fields,
98
+ or store instance variables to be used later.
6
99
 
7
100
 
8
101
  ## Verifying database records
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'active_cucumber'
6
- s.version = '0.0.3'
6
+ s.version = '0.0.4'
7
7
  s.authors = ['Kevin Goslar']
8
8
  s.email = ['kevin.goslar@gmail.com']
9
9
  s.summary = %s(ActiveRecord tools for Cucumber)
@@ -30,6 +30,20 @@ Feature: ActiveCucumber.create_many
30
30
  And the database contains the show "Star Trek TNG"
31
31
 
32
32
 
33
+ Scenario: creating associated objects that depend on other associated objects
34
+ When running "ActiveCucumber.create_many Episode, table" with this table:
35
+ | GENRE | SHOW | NAME |
36
+ | Science Fiction | Star Trek TNG | Encounter at Farpoint |
37
+ | Science Fiction | Star Trek TNG | All Good Things |
38
+ Then the database contains the shows:
39
+ | GENRE | NAME |
40
+ | Science Fiction | Star Trek TNG |
41
+ And the database contains the episodes:
42
+ | SHOW | NAME |
43
+ | Star Trek TNG | Encounter at Farpoint |
44
+ | Star Trek TNG | All Good Things |
45
+
46
+
33
47
  Scenario: complex example
34
48
  When running "ActiveCucumber.create_many Episode, table" with this table:
35
49
  | SHOW | NAME | YEAR |
@@ -6,7 +6,7 @@ Feature: ActiveCucumber.diff_all!
6
6
 
7
7
 
8
8
  Background:
9
- Given the TV episodes:
9
+ Given the episodes:
10
10
  | SHOW | NAME | YEAR |
11
11
  | Star Trek TNG | Encounter at Farpoint | 1987 |
12
12
  | Star Trek TNG | All Good Things | 1994 |
@@ -6,33 +6,33 @@ Feature: ActiveCucumber.diff_one!
6
6
 
7
7
 
8
8
  Background:
9
- Given the TV episode:
9
+ Given the episode:
10
10
  | SHOW | Star Trek TNG |
11
11
  | NAME | All Good Things |
12
12
  | YEAR | 1994 |
13
13
 
14
14
 
15
15
  Scenario: verifying string fields
16
- When running "ActiveCucumber.diff_one! @episode, table" with this table:
16
+ When running "ActiveCucumber.diff_one! @created_episode, table" with this table:
17
17
  | NAME | All Good Things |
18
18
  Then the test passes
19
19
 
20
20
 
21
21
  Scenario: verifying non-string fields
22
- When running "ActiveCucumber.diff_one! @episode, table" with this table:
22
+ When running "ActiveCucumber.diff_one! @created_episode, table" with this table:
23
23
  | YEAR | 1994 |
24
24
  Then the test passes
25
25
 
26
26
 
27
27
  Scenario: verifying associated fields
28
- When running "ActiveCucumber.diff_one! @episode, table" with this table:
28
+ When running "ActiveCucumber.diff_one! @created_episode, table" with this table:
29
29
  | SHOW | Star Trek TNG |
30
30
  | NAME | All Good Things |
31
31
  Then the test passes
32
32
 
33
33
 
34
34
  Scenario: complete table match
35
- When running "ActiveCucumber.diff_one! @episode, table" with this table:
35
+ When running "ActiveCucumber.diff_one! @created_episode, table" with this table:
36
36
  | SHOW | Star Trek TNG |
37
37
  | NAME | All Good Things |
38
38
  | YEAR | 1994 |
@@ -40,7 +40,7 @@ Feature: ActiveCucumber.diff_one!
40
40
 
41
41
 
42
42
  Scenario: providing a non-existing field
43
- When running "ActiveCucumber.diff_one! @episode, table" with this table:
43
+ When running "ActiveCucumber.diff_one! @created_episode, table" with this table:
44
44
  | NAME | All Good Things |
45
45
  | FOOBAR | 1994 |
46
46
  Then the test fails
@@ -48,7 +48,7 @@ Feature: ActiveCucumber.diff_one!
48
48
 
49
49
 
50
50
  Scenario: mismatching data in a table cell
51
- When running "ActiveCucumber.diff_one! @episode, table" with this table:
51
+ When running "ActiveCucumber.diff_one! @created_episode, table" with this table:
52
52
  | SHOW | Star Trek TOS |
53
53
  | NAME | All Good Things |
54
54
  Then the test fails
@@ -1,15 +1,12 @@
1
- Given(/^the TV episode:$/) do |table|
2
- data = ActiveCucumber.vertical_table table
3
- show = Show.find_or_create_by name: data['SHOW']
4
- @episode = show.episodes.create name: data['NAME'], year: data['YEAR']
5
- end
6
-
7
-
8
- Given(/^the TV episodes:$/) do |table|
9
- ActiveCucumber.horizontal_table(table).each do |row|
10
- show = Show.find_or_create_by name: row['SHOW']
11
- show.episodes.create name: row['NAME'], year: row['YEAR']
12
- end
1
+ Given(/^the (\w+):$/) do |class_name, table|
2
+ singular = class_name.singularize
3
+ clazz = singular.humanize.constantize
4
+ created_data = if (class_name == singular)
5
+ ActiveCucumber.create_one clazz, table
6
+ else
7
+ ActiveCucumber.create_many clazz, table
8
+ end
9
+ instance_variable_set "@created_#{class_name}", created_data
13
10
  end
14
11
 
15
12
 
@@ -38,6 +35,11 @@ Then(/^the database contains the given episodes$/) do
38
35
  end
39
36
 
40
37
 
38
+ Then(/^the database contains the (\w+):$/) do |class_name, table|
39
+ ActiveCucumber.diff_all! class_name.humanize.singularize.constantize, table
40
+ end
41
+
42
+
41
43
  Then(/^the database contains the shows? (.+)$/) do |show_names|
42
44
  expect(Show.all.map(&:name)).to match Kappamaki.from_sentence show_names
43
45
  end
@@ -46,8 +48,9 @@ end
46
48
  Then(/^the test (passes|fails)$/) do |expected_result|
47
49
  @error_checked = true
48
50
  if expected_result == 'passes' && @error_happened
49
- p @error_message
50
- p @exception
51
+ puts "\n#{@error_message}"
52
+ puts ''
53
+ @exception.backtrace.take(5).each { |trace| puts "in #{trace}" }
51
54
  end
52
55
  expect(@error_happened).to be expected_result != 'passes'
53
56
  end
@@ -15,13 +15,19 @@ ActiveRecord::Base.establish_connection(
15
15
 
16
16
 
17
17
  ActiveRecord::Schema.define do
18
+ create_table :genres, force: true do |t|
19
+ t.string :name
20
+ t.datetime 'created_at'
21
+ end
22
+
18
23
  create_table :shows, force: true do |t|
24
+ t.belongs_to :genre
19
25
  t.string :name
20
26
  t.datetime 'created_at'
21
27
  end
22
28
 
23
29
  create_table :episodes, force: true do |t|
24
- t.belongs_to :show, index: true
30
+ t.belongs_to :show
25
31
  t.string :name
26
32
  t.integer :year
27
33
  t.datetime 'created_at'
@@ -30,6 +36,10 @@ end
30
36
 
31
37
 
32
38
  FactoryGirl.define do
39
+ factory :genre do
40
+ name { Faker::Book.title }
41
+ end
42
+
33
43
  factory :show do
34
44
  name { Faker::Book.title }
35
45
  end
@@ -49,5 +59,10 @@ Before do
49
59
  end
50
60
 
51
61
  After do
52
- expect(@error_happened).to be false unless @error_checked
62
+ if @error_happened && !@error_checked
63
+ puts "\n#{@error_message}"
64
+ puts ''
65
+ @exception.backtrace.take(5).each { |trace| puts "in #{trace}" }
66
+ expect(@error_happened).to be false
67
+ end
53
68
  end
@@ -1,3 +1,3 @@
1
1
  class Episode < ActiveRecord::Base
2
- belongs_to :show, inverse_of: :episodes, required: true
2
+ belongs_to :show, required: true
3
3
  end
@@ -1,7 +1,12 @@
1
- class EpisodeCureator < ActiveCucumber::Cureator
1
+ class EpisodeCreator < ActiveCucumber::Creator
2
2
 
3
3
  def value_for_show show_name
4
- Show.find_by(name: show_name) || FactoryGirl.create(:show, name: show_name)
4
+ Show.find_by(name: show_name) || FactoryGirl.create(:show, name: show_name, genre: @genre)
5
+ end
6
+
7
+ def value_for_genre genre_name
8
+ @genre = Genre.find_by(name: genre_name) || FactoryGirl.create(:genre, name: genre_name)
9
+ delete :genre
5
10
  end
6
11
 
7
12
  end
@@ -0,0 +1,3 @@
1
+ class Genre < ActiveRecord::Base
2
+ has_many :shows
3
+ end
@@ -1,3 +1,4 @@
1
1
  class Show < ActiveRecord::Base
2
- has_many :episodes, inverse_of: :show
2
+ belongs_to :genre
3
+ has_many :episodes
3
4
  end
@@ -0,0 +1,7 @@
1
+ class ShowCucumberator < ActiveCucumber::Cucumberator
2
+
3
+ def value_for_genre
4
+ genre.name
5
+ end
6
+
7
+ end
@@ -0,0 +1,44 @@
1
+ module ActiveCucumber
2
+
3
+ # Creates ActiveRecord entries with data from given Cucumber tables.
4
+ class ActiveRecordBuilder
5
+
6
+ def initialize activerecord_class
7
+ @clazz = activerecord_class
8
+ @creator_class = creator_class
9
+ end
10
+
11
+
12
+ # Creates all entries in the given horizontal table hash
13
+ def create_many table
14
+ table.map do |row|
15
+ create_record row
16
+ end
17
+ end
18
+
19
+
20
+ # Creates a new record with the given attributes in the database
21
+ def create_record attributes
22
+ creator = @creator_class.new attributes
23
+ FactoryGirl.create @clazz, creator.factorygirl_attributes
24
+ end
25
+
26
+
27
+ private
28
+
29
+ # Returns the Cucumberator subclass to be used by this Cucumparer instance
30
+ def creator_class
31
+ creator_class_name.constantize
32
+ rescue NameError
33
+ Creator
34
+ end
35
+
36
+
37
+ # Returns the name of the Cucumberator subclass to be used by this Cucumparer instance.
38
+ def creator_class_name
39
+ "#{@clazz.name}Creator"
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,59 @@
1
+ module ActiveCucumber
2
+
3
+ # Converts an attributes hash in Cucumber format
4
+ # into a hash consumable by FactoryGirl
5
+ #
6
+ # Subclasses define methods to convert particular fields.
7
+ class Creator
8
+
9
+ def initialize attributes
10
+ @attributes = attributes
11
+ end
12
+
13
+ # Returns the FactoryGirl version of this Creator's attributes
14
+ def factorygirl_attributes
15
+ symbolize_attributes!
16
+ @attributes.each do |key, value|
17
+ next unless respond_to?(method = method_name(key))
18
+ if (result = send method, value)
19
+ @attributes[key] = result if @attributes.key? key
20
+ else
21
+ @attributes.delete key
22
+ end
23
+ end
24
+ end
25
+
26
+
27
+ private
28
+
29
+ def method_missing method_name, *arguments
30
+ # This is necessary so that a Creator subclass can access
31
+ # methods of @attributes as if they were its own.
32
+ @attributes.send method_name, *arguments
33
+ end
34
+
35
+
36
+ # Returns the name of the value_for method for the given key
37
+ def method_name key
38
+ "value_for_#{key}"
39
+ end
40
+
41
+
42
+ # Converts the key given in Cucumber format into FactoryGirl format
43
+ def normalized_key key
44
+ key.downcase.parameterize.underscore.to_sym
45
+ end
46
+
47
+
48
+ # Makes the keys on @attributes be normalized symbols
49
+ def symbolize_attributes!
50
+ @attributes = {}.tap do |result|
51
+ @attributes.each do |key, value|
52
+ result[normalized_key key] = value
53
+ end
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -1,20 +1,23 @@
1
1
  require 'active_cucumber/cucumparer'
2
2
  require 'active_cucumber/cucumberator'
3
- require 'active_cucumber/cureator'
3
+ require 'active_cucumber/active_record_builder'
4
+ require 'active_cucumber/creator'
4
5
 
5
6
  # The main namespace for this gem
6
7
  module ActiveCucumber
7
8
 
8
9
  # Creates entries of the given ActiveRecord class
9
- # specified by the given Cucumber table
10
+ # specified by the given horizontal Cucumber table
10
11
  def self.create_many activerecord_class, cucumber_table
11
- Cureator.for(activerecord_class).create_records ActiveCucumber.horizontal_table(cucumber_table)
12
+ builder = ActiveRecordBuilder.new activerecord_class
13
+ builder.create_many ActiveCucumber.horizontal_table(cucumber_table)
12
14
  end
13
15
 
14
16
  # Creates an entry of the given ActiveRecord class
15
- # specified by the given Cucumber table
17
+ # specified by the given vertical Cucumber table
16
18
  def self.create_one activerecord_class, cucumber_table
17
- Cureator.for(activerecord_class).create_record ActiveCucumber.vertical_table(cucumber_table)
19
+ builder = ActiveRecordBuilder.new activerecord_class
20
+ builder.create_record ActiveCucumber.vertical_table(cucumber_table)
18
21
  end
19
22
 
20
23
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_cucumber
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Goslar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-22 00:00:00.000000000 Z
11
+ date: 2015-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -220,11 +220,14 @@ files:
220
220
  - features/support/episode.rb
221
221
  - features/support/episode_cucumberator.rb
222
222
  - features/support/episode_cureator.rb
223
+ - features/support/genre.rb
223
224
  - features/support/show.rb
225
+ - features/support/show_cucumberator.rb
224
226
  - lib/active_cucumber.rb
227
+ - lib/active_cucumber/active_record_builder.rb
228
+ - lib/active_cucumber/creator.rb
225
229
  - lib/active_cucumber/cucumberator.rb
226
230
  - lib/active_cucumber/cucumparer.rb
227
- - lib/active_cucumber/cureator.rb
228
231
  homepage: https://github.com/Originate/active_cucumber
229
232
  licenses:
230
233
  - MIT
@@ -1,86 +0,0 @@
1
- module ActiveCucumber
2
-
3
- # Creates ActiveRecord entries
4
- class Cureator
5
-
6
- # Returns the Cureator instance for the given ActiveRecord class.
7
- def self.for activerecord_class
8
- cureator_class(activerecord_class).new activerecord_class
9
- end
10
-
11
-
12
- def initialize activerecord_class
13
- @clazz = activerecord_class
14
- end
15
-
16
-
17
- # Creates all entries in the given Cucumber table
18
- #
19
- # Assumes a horizontal Cucumber table.
20
- def create_records table
21
- table.map do |row|
22
- create_record row
23
- end
24
- end
25
-
26
-
27
- # Creates a new record in the database,
28
- # of the given class, with the given Cucumber-formatted String attributes.
29
- def create_record attributes
30
- FactoryGirl.create @clazz, factorygirl_row(symbolized_hash(attributes))
31
- end
32
-
33
-
34
- private
35
-
36
- # Returns the Cucumberator subclass to be used by this Cucumparer instance
37
- def self.cureator_class activerecord_class
38
- cureator_class_name(activerecord_class).constantize
39
- rescue NameError
40
- Cureator
41
- end
42
-
43
-
44
- # Returns the name of the Cucumberator subclass to be used by this Cucumparer instance.
45
- def self.cureator_class_name activerecord_class
46
- "#{activerecord_class.name}Cureator"
47
- end
48
-
49
-
50
- # Returns the given row, with values converted to FactoryGirl format
51
- #
52
- # Assumes the keys of the row are hashes
53
- def factorygirl_row row
54
- {}.tap do |result|
55
- row.each do |key, value|
56
- method = method_name key
57
- result[key] = respond_to?(method) ? send(method, value) : value
58
- end
59
- end
60
- end
61
-
62
-
63
- # Returns the name of the value_for method for the given key
64
- def method_name key
65
- "value_for_#{key}"
66
- end
67
-
68
-
69
- # Converts the key given in Cucumber format into FactoryGirl format
70
- def normalized_key key
71
- key.downcase.parameterize.underscore
72
- end
73
-
74
-
75
- # Returns a new hash with the keys normalized to symbols
76
- def symbolized_hash row
77
- {}.tap do |result|
78
- row.each do |key, value|
79
- result[normalized_key key] = value
80
- end
81
- end
82
- end
83
-
84
- end
85
-
86
- end