kookaburra 1.3.1 → 2.0.0

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.
@@ -1,214 +1,65 @@
1
- require 'restclient'
2
- require 'core_ext/object/to_query'
3
- require 'kookaburra/exceptions'
1
+ require 'forwardable'
4
2
 
5
3
  class Kookaburra
6
- # Communicate with a Web Services API
4
+ # Your APIDriver subclass is used to define your testing DSL for setting up
5
+ # test preconditions. Unlike {Kookaburra::APIClient}, which is meant to be a
6
+ # simple mapping to your application's API, a method in the APIDriver may be
7
+ # comprised of several distinct API calls as well as access to Kookaburra's
8
+ # test data store via {#mental_model}.
7
9
  #
8
- # You will create a subclass of {APIDriver} in your testing
9
- # implementation to be used with you subclass of
10
- # {Kookaburra::GivenDriver}. While the {GivenDriver} implements the
11
- # "business domain" DSL for setting up your application state, the
12
- # {APIDriver} maps discreet operations to your application's web
13
- # service API and can (optionally) handle encoding input data and
14
- # decoding response bodies to and from your preferred serialization
15
- # format.
10
+ # @abstract Subclass and implement your Given DSL.
11
+ #
12
+ # @example APIDriver subclass
13
+ # module MyApp
14
+ # module Kookaburra
15
+ # class APIDriver < ::Kookaburra::APIDriver
16
+ # def api
17
+ # @api ||= APIClient.new(configuration)
18
+ # end
19
+ #
20
+ # def a_widget(name, attributes = {})
21
+ # # Set up the data that will be passed to the API by merging any
22
+ # # passed attributes into the default data.
23
+ # data = {:name => 'Foo', :description => 'Bar baz'}.merge(attributes)
24
+ #
25
+ # # Call the API method and get the resulting response as Ruby data.
26
+ # result = api.create_widget(data)
27
+ #
28
+ # # Store the resulting widget data in the MentalModel object, so that
29
+ # # it can be referenced in other operations.
30
+ # mental_model.widgets[name] = result
31
+ # end
32
+ # end
33
+ # end
34
+ # end
16
35
  class APIDriver
17
- class << self
18
- # Serializes input data
19
- #
20
- # If specified, any input data provided to {APIDriver#post},
21
- # {APIDriver#put} or {APIDriver#request} will be processed through
22
- # this function prior to being sent to the HTTP server.
23
- #
24
- # @yieldparam data [Object] The data parameter that was passed to
25
- # the request method
26
- # @yieldreturn [String] The text to be used as the request body
27
- #
28
- # @example
29
- # class MyAPIDriver < Kookaburra::APIDriver
30
- # encode_with { |data| JSON.dump(data) }
31
- # # ...
32
- # end
33
- def encode_with(&block)
34
- define_method(:encode) do |data|
35
- return if data.nil?
36
- block.call(data)
37
- end
38
- end
39
-
40
- # Deserialize response body
41
- #
42
- # If specified, the response bodies of all requests made using
43
- # this {APIDriver} will be processed through this function prior
44
- # to being returned.
45
- #
46
- # @yieldparam data [String] The response body sent by the HTTP
47
- # server
48
- #
49
- # @yieldreturn [Object] The result of parsing the response body
50
- # through this function
51
- #
52
- # @example
53
- # class MyAPIDriver < Kookaburra::APIDriver
54
- # decode_with { |data| JSON.parse(data) }
55
- # # ...
56
- # end
57
- def decode_with(&block)
58
- define_method(:decode) do |data|
59
- block.call(data)
60
- end
61
- end
36
+ extend Forwardable
62
37
 
63
- # Set custom HTTP headers
64
- #
65
- # Can be called multiple times to set HTTP headers that will be
66
- # provided with every request made by the {APIDriver}.
67
- #
68
- # @param [String] name The name of the header, e.g. 'Content-Type'
69
- # @param [String] value The value to which the header is set
70
- #
71
- # @example
72
- # class MyAPIDriver < Kookaburra::APIDriver
73
- # header 'Content-Type', 'application/json'
74
- # header 'Accept', 'application/json'
75
- # # ...
76
- # end
77
- def header(name, value)
78
- headers[name] = value
79
- end
80
-
81
- # Used to retrieve the list of headers within the instance. Not
82
- # intended to be used elsewhere.
83
- #
84
- # @private
85
- def headers
86
- @headers ||= {}
87
- end
88
- end
89
-
90
- # Create a new {APIDriver} instance
38
+ # It is unlikely that you would call #initialize yourself; your APIDriver
39
+ # object is instantiated for you by {Kookaburra#given}.
91
40
  #
92
41
  # @param [Kookaburra::Configuration] configuration
93
- # @param [RestClient] http_client (optional) Generally only
94
- # overriden when testing Kookaburra itself
95
- def initialize(configuration, http_client = RestClient)
42
+ def initialize(configuration)
96
43
  @configuration = configuration
97
- @http_client = http_client
98
44
  end
99
45
 
100
- # Convenience method to make a POST request
101
- #
102
- # @see APIDriver#request
103
- def post(path, data = nil, headers = {})
104
- request(:post, path, data, headers)
105
- end
46
+ protected
106
47
 
107
- # Convenience method to make a PUT request
108
- #
109
- # @see APIDriver#request
110
- def put(path, data = nil, headers = {})
111
- request(:put, path, data, headers)
112
- end
48
+ attr_reader :configuration
113
49
 
114
- # Convenience method to make a GET request
50
+ # Access to the shared {Kookaburra::MentalModel} instance
115
51
  #
116
- # @see APIDriver#request
117
- def get(path, data = nil, headers = {})
118
- path = add_querystring_to_path(path, data)
119
- request(:get, path, nil, headers)
120
- end
52
+ # @attribute [rw] mental_model
53
+ def_delegator :configuration, :mental_model
121
54
 
122
- # Convenience method to make a DELETE request
123
- #
124
- # @see APIDriver#request
125
- def delete(path, data = nil, headers = {})
126
- path = add_querystring_to_path(path, data)
127
- request(:delete, path, nil, headers)
128
- end
129
-
130
- # Make an HTTP request
131
- #
132
- # If you need to make a request other than the typical GET, POST,
133
- # PUT and DELETE, you can use this method directly.
134
- #
135
- # This *will* follow redirects when the server's response code is in
136
- # the 3XX range. If the response is a 303, the request will be
137
- # transformed into a GET request.
138
- #
139
- # @see APIDriver.encode_with
140
- # @see APIDriver.decode_with
141
- # @see APIDriver.header
142
- # @see APIDriver#get
143
- # @see APIDriver#post
144
- # @see APIDriver#put
145
- # @see APIDriver#delete
55
+ # Used to access your APIClient in your own APIDriver implementation
146
56
  #
147
- # @param [Symbol] method The HTTP verb to use with the request
148
- # @param [String] path The path to request. Will be joined with the
149
- # {Kookaburra::Configuration#app_host} setting to build the
150
- # URL unless a full URL is specified here.
151
- # @param [Object] data The data to be posted in the request body. If
152
- # an encoder was specified, this can be any type of object as
153
- # long as the encoder can serialize it into a String. If no
154
- # encoder was specified, then this can be one of:
155
- #
156
- # * a String - will be passed as is
157
- # * a Hash - will be encoded as normal HTTP form params
158
- # * a Hash containing references to one or more Files - will
159
- # set the content type to multipart/form-data
160
- #
161
- # @return [Object] The response body returned by the server. If a
162
- # decoder was specified, this will return the result of
163
- # parsing the response body through the decoder function.
164
- #
165
- # @raise [Kookaburra::UnexpectedResponse] Raised if the HTTP
166
- # response received is not in the 2XX-3XX range.
167
- def request(method, path, data, headers)
168
- data = encode(data)
169
- headers = global_headers.merge(headers)
170
- response = @http_client.send(method, url_for(path), *[data, headers].compact)
171
- decode(response.body)
172
- rescue RestClient::Exception => e
173
- raise_unexpected_response(e)
174
- end
175
-
176
- private
177
-
178
- def add_querystring_to_path(path, data)
179
- return path if data.nil? || data == {}
180
- "#{path}?#{data.to_query}"
181
- end
182
-
183
- def global_headers
184
- self.class.headers
185
- end
186
-
187
- def url_for(path)
188
- URI.join(base_url, path).to_s
189
- end
190
-
191
- def base_url
192
- @configuration.app_host
193
- end
194
-
195
- def encode(data)
196
- data
197
- end
198
-
199
- def decode(data)
200
- data
201
- end
202
-
203
- def raise_unexpected_response(exception)
204
- message = <<-END
205
- Unexpected response from server: #{exception.message}
206
-
207
- #{exception.http_body}
208
- END
209
- new_exception = UnexpectedResponse.new(message)
210
- new_exception.set_backtrace(exception.backtrace)
211
- raise new_exception
57
+ # @abstract
58
+ # @return [Kookaburra::APIClient]
59
+ # @raise [Kookaburra::ConfigurationError] raised if you do not provide an
60
+ # implementation.
61
+ def api
62
+ raise ConfigurationError, "You must implement #api in your subclass."
212
63
  end
213
64
  end
214
65
  end
@@ -7,12 +7,12 @@ class Kookaburra
7
7
  class Configuration
8
8
  extend DependencyAccessor
9
9
 
10
- # The class to use as your GivenDriver
10
+ # The class to use as your APIDriver
11
11
  #
12
- # @attribute [rw] given_driver_class
12
+ # @attribute [rw] api_driver_class
13
13
  # @raise [Kookaburra::ConfigurationError] if you try to read this attribute
14
14
  # without it having been set
15
- dependency_accessor :given_driver_class
15
+ dependency_accessor :api_driver_class
16
16
 
17
17
  # The class to use as your UIDriver
18
18
  #
@@ -39,7 +39,7 @@ class Kookaburra
39
39
  dependency_accessor :app_host
40
40
 
41
41
  # This is the {Kookaburra::MentalModel} that is shared between your
42
- # GivenDriver and your UIDriver. This attribute is managed by {Kookaburra},
42
+ # APIDriver and your UIDriver. This attribute is managed by {Kookaburra},
43
43
  # so you shouldn't need to change it yourself.
44
44
  #
45
45
  # @attribute [rw] mental_model
@@ -4,7 +4,7 @@ require 'kookaburra/exceptions'
4
4
  class Kookaburra
5
5
  # Each instance of {Kookaburra} has its own instance of MentalModel. This object
6
6
  # is used to maintain a shared understanding of the application state between
7
- # your {GivenDriver} and your {UIDriver}. You can access the various test data
7
+ # your {APIDriver} and your {UIDriver}. You can access the various test data
8
8
  # collections in your test implementations via {Kookaburra#get_data}.
9
9
  #
10
10
  # The mental model is not intended to represent a copy of all of the data
@@ -48,11 +48,13 @@ class Kookaburra
48
48
  # # Raises an UnknownKeyError
49
49
  # mental_model.widgets[:bar]
50
50
  class Collection < SimpleDelegator
51
+ attr_reader :name
52
+
51
53
  # @param [String] name The name of the collection. Used to provide
52
54
  # helpful error messages when unknown keys are accessed.
53
55
  # @param [Hash] init_data Preloads specific data into the collection
54
56
  def initialize(name, init_data = nil)
55
- @name = name
57
+ self.name = name
56
58
  data = Hash.new do |hash, key|
57
59
  raise UnknownKeyError, "Can't find mental_model.#{@name}[#{key.inspect}]. Did you forget to set it?"
58
60
  end
@@ -119,7 +121,7 @@ class Kookaburra
119
121
  #
120
122
  # @return [Kookaburra::MentalModel::Collection] the deleted items subcollection
121
123
  def deleted
122
- @deleted ||= self.class.new("deleted")
124
+ @deleted ||= self.class.new("#{name}.deleted")
123
125
  end
124
126
 
125
127
  # Deletes key/value pairs from the collection for which the given block evaluates
@@ -137,8 +139,18 @@ class Kookaburra
137
139
  def dup
138
140
  new_data = {}.merge(self)
139
141
  new_data = Marshal.load(Marshal.dump(new_data))
140
- self.class.new(@name, new_data)
142
+ self.class.new(@name, new_data).tap do |mm|
143
+ mm.deleted = deleted.dup unless deleted.empty?
144
+ end
141
145
  end
146
+
147
+ protected
148
+
149
+ attr_writer :deleted
150
+
151
+ private
152
+
153
+ attr_writer :name
142
154
  end
143
155
  end
144
156
  end
@@ -1,6 +1,5 @@
1
1
  require 'forwardable'
2
2
  require 'kookaburra'
3
- require 'kookaburra/mental_model_matcher'
4
3
 
5
4
  class Kookaburra
6
5
  # This module is intended to be mixed in to your testing context to provide
@@ -11,12 +10,12 @@ class Kookaburra
11
10
  # @example RSpec setup
12
11
  # # in 'spec/support/kookaburra_setup.rb'
13
12
  # require 'kookaburra/test_helpers'
14
- # require 'my_app/kookaburra/given_driver'
13
+ # require 'my_app/kookaburra/api_driver'
15
14
  # require 'my_app/kookaburra/ui_driver'
16
- #
15
+ #
17
16
  # Kookaburra.configure do |c|
18
- # c.given_driver_class = myapp::kookaburra::givendriver,
19
- # c.ui_driver_class = myapp::kookaburra::uidriver,
17
+ # c.api_driver_class = MyApp::Kookaburra::APIDriver,
18
+ # c.ui_driver_class = MyApp::Kookaburra::UIDriver,
20
19
  # c.app_host = 'http://my_app.example.com:12345',
21
20
  # c.browser = capybara,
22
21
  # c.server_error_detection { |browser|
@@ -31,9 +30,9 @@ class Kookaburra
31
30
  # # in 'spec/request/some_feature_spec.rb'
32
31
  # describe "Some Feature" do
33
32
  # example "some test script" do
34
- # given.a_widget(:foo)
35
- # given.a_widget(:bar, :hidden => true)
36
- # given.a_widget(:baz)
33
+ # api.create_widget(:foo)
34
+ # api.create_widget(:bar, :hidden => true)
35
+ # api.create_widget(:baz)
37
36
  #
38
37
  # ui.view_list_of_widgets
39
38
  #
@@ -48,12 +47,12 @@ class Kookaburra
48
47
  # @example Cucumber setup
49
48
  # # in 'features/support/kookaburra_setup.rb'
50
49
  # require 'kookaburra/test_helpers'
51
- # require 'my_app/kookaburra/given_driver'
50
+ # require 'my_app/kookaburra/api_driver'
52
51
  # require 'my_app/kookaburra/ui_driver'
53
- #
52
+ #
54
53
  # Kookaburra.configure do |c|
55
- # c.given_driver_class = myapp::kookaburra::givendriver,
56
- # c.ui_driver_class = myapp::kookaburra::uidriver,
54
+ # c.api_driver_class = MyApp::Kookaburra::APIDriver,
55
+ # c.ui_driver_class = MyApp::Kookaburra::UIDriver,
57
56
  # c.app_host = 'http://my_app.example.com:12345',
58
57
  # c.browser = capybara,
59
58
  # c.server_error_detection { |browser|
@@ -65,11 +64,11 @@ class Kookaburra
65
64
  #
66
65
  # # in 'features/step_definitions/some_steps.rb
67
66
  # Given /^there is a widget, "(\w+)"/ do |name|
68
- # given.a_widget(name.to_sym)
67
+ # api.create_widget(name.to_sym)
69
68
  # end
70
69
  #
71
70
  # Given /^there is a hidden widget, "(\w+)"/ do |name|
72
- # given.a_widget(name.to_sym, :hidden => true)
71
+ # api.create_widget(name.to_sym, :hidden => true)
73
72
  # end
74
73
  #
75
74
  # When /^I view the widget list/ do
@@ -92,33 +91,12 @@ class Kookaburra
92
91
  @k ||= Kookaburra.new
93
92
  end
94
93
 
95
- # @method given
94
+ # @method api
96
95
  # Delegates to {#k}
97
- def_delegator :k, :given
96
+ def_delegator :k, :api
98
97
 
99
98
  # @method ui
100
99
  # Delegates to {#k}
101
100
  def_delegator :k, :ui
102
-
103
- # RSpec-style custom matcher that compares a given array with
104
- # the current state of one named collection in the mental model
105
- #
106
- # @see Kookaburra::MentalModel::Matcher
107
- def match_mental_model_of(collection_key)
108
- MentalModel::Matcher.new(k.send(:__mental_model__), collection_key)
109
- end
110
-
111
- # Custom assertion for Test::Unit-style tests
112
- # (really, anything that uses #assert(predicate, message = nil))
113
- #
114
- # @see Kookaburra::MentalModel::Matcher
115
- def assert_mental_model_matches(collection_key, actual, message = nil)
116
- matcher = match_mental_model_of(collection_key)
117
- result = matcher.matches?(actual)
118
- return if !!result # don't even bother
119
-
120
- message ||= matcher.failure_message_for_should
121
- assert result, message
122
- end
123
101
  end
124
102
  end
@@ -1,3 +1,3 @@
1
1
  class Kookaburra
2
- VERSION = "1.3.1"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -1,352 +1,17 @@
1
1
  require 'kookaburra/test_helpers'
2
- require 'kookaburra/api_driver'
2
+ require 'kookaburra/api_client'
3
3
  require 'kookaburra/rack_app_server'
4
4
  require 'capybara'
5
5
  require 'capybara/webkit'
6
6
  require 'uuid'
7
7
 
8
- # These are required for the Rack app used for testing
9
- require 'sinatra/base'
10
- require 'json'
8
+ require 'support/json_api_app_and_kookaburra_drivers'
11
9
 
12
10
  describe "testing a Rack application with Kookaburra" do
13
11
  include Kookaburra::TestHelpers
14
12
 
15
13
  describe "with an HTML interface" do
16
14
  describe "with a JSON API" do
17
- # This is the fixture Rack application against which the integration
18
- # test will run. It uses class variables to persist data, because
19
- # Sinatra will instantiate a new instance of TestRackApp for each
20
- # request.
21
- class JsonApiApp < Sinatra::Base
22
- enable :sessions
23
- disable :show_exceptions
24
-
25
- def parse_json_req_body
26
- request.body.rewind
27
- JSON.parse(request.body.read, :symbolize_names => true)
28
- end
29
-
30
- post '/users' do
31
- user_data = parse_json_req_body
32
- @@users ||= {}
33
- @@users[user_data[:email]] = user_data
34
- status 201
35
- headers 'Content-Type' => 'application/json'
36
- body user_data.to_json
37
- end
38
-
39
- post '/session' do
40
- user = @@users[params[:email]]
41
- if user && user[:password] == params[:password]
42
- session[:logged_in] = true
43
- status 200
44
- body 'You are logged in!'
45
- else
46
- session[:logged_in] = false
47
- status 403
48
- body 'Log in failed!'
49
- end
50
- end
51
-
52
- get '/session/new' do
53
- body <<-EOF
54
- <html>
55
- <head>
56
- <title>Sign In</title>
57
- </head>
58
- <body>
59
- <div id="sign_in_screen">
60
- <form action="/session" method="POST">
61
- <label for="email">Email:</label>
62
- <input id="email" name="email" type="text" />
63
-
64
- <label for="password">Password:</label>
65
- <input id="password" name="password" type="password" />
66
-
67
- <input type="submit" value="Sign In" />
68
- </form>
69
- </div>
70
- </body>
71
- </html>
72
- EOF
73
- end
74
-
75
- post '/widgets/:widget_id' do
76
- @@widgets.delete_if do |w|
77
- w[:id] == params['widget_id']
78
- end
79
- redirect to('/widgets')
80
- end
81
-
82
- get '/widgets/new' do
83
- body <<-EOF
84
- <html>
85
- <head>
86
- <title>New Widget</title>
87
- </head>
88
- <body>
89
- <div id="widget_form">
90
- <form action="/widgets" method="POST">
91
- <label for="name">Name:</label>
92
- <input id="name" name="name" type="text" />
93
-
94
- <input type="submit" value="Save" />
95
- </form>
96
- </div>
97
- </body>
98
- </html>
99
- EOF
100
- end
101
-
102
- post '/widgets' do
103
- @@widgets ||= []
104
- widget_data = if request.media_type == 'application/json'
105
- parse_json_req_body
106
- else
107
- {:name => params['name']}
108
- end
109
- widget_data[:id] = UUID.new.generate
110
- @@widgets << widget_data
111
- @@last_widget_created = widget_data
112
- request.accept.each do |type|
113
- case type.to_s
114
- when 'application/json'
115
- status 201
116
- headers 'Content-Type' => 'application/json'
117
- halt widget_data.to_json
118
- else
119
- halt redirect to('/widgets')
120
- end
121
- end
122
- end
123
-
124
- get '/widgets' do
125
- raise "Not logged in!" unless session[:logged_in]
126
- @@widgets ||= []
127
- last_widget_created, @@last_widget_created = @@last_widget_created, nil
128
- content = ''
129
- content << <<-EOF
130
- <html>
131
- <head>
132
- <title>Widgets</title>
133
- </head>
134
- <body>
135
- <div id="widget_list">
136
- EOF
137
- if last_widget_created
138
- content << <<-EOF
139
- <div class="last_widget created">
140
- <span class="id">#{last_widget_created[:id]}</span>
141
- <span class="name">#{last_widget_created[:name]}</span>
142
- </div>
143
- EOF
144
- end
145
- content << <<-EOF
146
- <ul>
147
- EOF
148
- @@widgets.each do |w|
149
- content << <<-EOF
150
- <li class="widget_summary">
151
- <span class="id">#{w[:id]}</span>
152
- <span class="name">#{w[:name]}</span>
153
- <form id="delete_#{w[:id]}" action="/widgets/#{w[:id]}" method="POST">
154
- <button type="submit" value="Delete" />
155
- </form>
156
- </li>
157
- EOF
158
- end
159
- content << <<-EOF
160
- </ul>
161
- <a href="/widgets/new">New Widget</a>
162
- </div>
163
- </body>
164
- </html>
165
- EOF
166
- body content
167
- end
168
-
169
- get '/error_page' do
170
- content = <<-EOF
171
- <html>
172
- <head>
173
- <title>Internal Server Error</title>
174
- </head>
175
- <body>
176
- <p>A Purposeful Error</p>
177
- </body>
178
- </html>
179
- EOF
180
- body content
181
- end
182
-
183
- error do
184
- e = request.env['sinatra.error']
185
- body << <<-EOF
186
- <html>
187
- <head>
188
- <title>Internal Server Error</title>
189
- </head>
190
- <body>
191
- <pre>
192
- #{e.to_s}\n#{e.backtrace.join("\n")}
193
- </pre>
194
- </body>
195
- </html>
196
- EOF
197
- end
198
- end
199
-
200
- class MyAPIDriver < Kookaburra::APIDriver
201
- encode_with { |data| JSON.dump(data) }
202
- decode_with { |data| JSON.parse(data) }
203
- header 'Content-Type', 'application/json'
204
- header 'Accept', 'application/json'
205
-
206
- def create_user(user_data)
207
- post '/users', user_data
208
- end
209
-
210
- def create_widget(widget_data)
211
- post '/widgets', widget_data
212
- end
213
- end
214
-
215
- class MyGivenDriver < Kookaburra::GivenDriver
216
- def api
217
- MyAPIDriver.new(configuration)
218
- end
219
-
220
- def a_user(name)
221
- user = {'email' => 'bob@example.com', 'password' => '12345'}
222
- result = api.create_user(user)
223
- mental_model.users[name] = result
224
- end
225
-
226
- def a_widget(name, attributes = {})
227
- widget = {'name' => 'Foo'}.merge(attributes)
228
- result = api.create_widget(widget)
229
- mental_model.widgets[name] = result
230
- end
231
- end
232
-
233
- class SignInScreen < Kookaburra::UIDriver::UIComponent
234
- def component_path
235
- '/session/new'
236
- end
237
-
238
- # Use default component locator value
239
- #
240
- # def component_locator
241
- # '#sign_in_screen'
242
- # end
243
-
244
- def sign_in(user_data)
245
- fill_in 'Email:', :with => user_data['email']
246
- fill_in 'Password:', :with => user_data['password']
247
- click_button 'Sign In'
248
- end
249
- end
250
-
251
- class ErrorPage < Kookaburra::UIDriver::UIComponent
252
- def component_path
253
- '/error_page'
254
- end
255
- end
256
-
257
- class WidgetDataContainer
258
- def initialize(element)
259
- @element = element
260
- end
261
-
262
- def to_hash
263
- {
264
- 'id' => @element.find('.id').text,
265
- 'name' => @element.find('.name').text
266
- }
267
- end
268
- end
269
-
270
- class LastWidgetCreated < Kookaburra::UIDriver::UIComponent
271
- def component_locator
272
- @options[:component_locator]
273
- end
274
-
275
- def data
276
- raise "Foo" unless visible?
277
- WidgetDataContainer.new(self).to_hash
278
- end
279
- end
280
-
281
- class WidgetList < Kookaburra::UIDriver::UIComponent
282
- ui_component :last_widget_created, LastWidgetCreated, :component_locator => '#widget_list .last_widget.created'
283
-
284
- def component_path
285
- '/widgets'
286
- end
287
-
288
- def component_locator
289
- '#widget_list'
290
- end
291
-
292
- def widgets
293
- all('.widget_summary').map do |el|
294
- WidgetDataContainer.new(el).to_hash
295
- end
296
- end
297
-
298
- def choose_to_create_new_widget
299
- click_on 'New Widget'
300
- end
301
-
302
- def choose_to_delete_widget(widget_data)
303
- find("#delete_#{widget_data['id']}").click_button('Delete')
304
- end
305
- end
306
-
307
- class WidgetForm < Kookaburra::UIDriver::UIComponent
308
- def component_locator
309
- '#widget_form'
310
- end
311
-
312
- def submit(widget_data)
313
- fill_in 'Name:', :with => widget_data['name']
314
- click_on 'Save'
315
- end
316
- end
317
-
318
- class MyUIDriver < Kookaburra::UIDriver
319
- ui_component :error_page, ErrorPage
320
- ui_component :sign_in_screen, SignInScreen
321
- ui_component :widget_list, WidgetList
322
- ui_component :widget_form, WidgetForm
323
-
324
- def sign_in(name)
325
- address_bar.go_to sign_in_screen
326
- sign_in_screen.sign_in(mental_model.users[name])
327
- end
328
-
329
- def error_on_purpose
330
- address_bar.go_to error_page
331
- end
332
-
333
- def view_widget_list
334
- address_bar.go_to widget_list
335
- end
336
-
337
- def create_new_widget(name, attributes = {})
338
- assert widget_list.visible?, "Widget list is not visible!"
339
- widget_list.choose_to_create_new_widget
340
- widget_form.submit('name' => 'My Widget')
341
- mental_model.widgets[name] = widget_list.last_widget_created.data
342
- end
343
-
344
- def delete_widget(name)
345
- assert widget_list.visible?, "Widget list is not visible!"
346
- widget_list.choose_to_delete_widget(mental_model.widgets.delete(name))
347
- end
348
- end
349
-
350
15
  app_server = Kookaburra::RackAppServer.new do
351
16
  JsonApiApp.new
352
17
  end
@@ -356,7 +21,7 @@ describe "testing a Rack application with Kookaburra" do
356
21
 
357
22
  Kookaburra.configure do |c|
358
23
  c.ui_driver_class = MyUIDriver
359
- c.given_driver_class = MyGivenDriver
24
+ c.api_driver_class = MyAPIDriver
360
25
  c.app_host = 'http://127.0.0.1:%d' % app_server.port
361
26
  c.browser = Capybara::Session.new(:webkit)
362
27
  c.server_error_detection do |browser|
@@ -369,32 +34,45 @@ describe "testing a Rack application with Kookaburra" do
369
34
  app_server.shutdown
370
35
  end
371
36
 
372
- it "runs the tests against the app" do
373
- given.a_user(:bob)
374
- given.a_widget(:widget_a)
375
- given.a_widget(:widget_b, :name => 'Foo')
37
+ before(:each) do
38
+ api.create_user(:bob)
39
+ api.create_widget(:widget_a)
40
+ api.create_widget(:widget_b, :name => 'Widget B')
41
+ end
42
+
43
+ define_method(:widgets) { k.get_data(:widgets) }
376
44
 
45
+ it "runs the tests against the application's UI" do
377
46
  ui.sign_in(:bob)
378
- ui.view_widget_list
379
47
 
380
- # The following two lines are two different ways to shave the yak, but
381
- # the second one does more to match against the full state of the mental
382
- # model, provides better failure messages, and is shorter.
383
- ui.widget_list.widgets.should == k.get_data(:widgets).values_at(:widget_a, :widget_b)
384
- ui.widget_list.widgets.should match_mental_model_of(:widgets)
48
+ ui.view_widget_list
49
+ expect(ui.widget_list.widgets).to include widgets[:widget_a]
50
+ expect(ui.widget_list.widgets).to include widgets[:widget_b]
385
51
 
386
- ui.create_new_widget(:widget_c, :name => 'Bar')
52
+ ui.create_widget(:widget_c, :name => 'Bar')
53
+ expect(ui.widget_list.widgets).to include widgets[:widget_a]
54
+ expect(ui.widget_list.widgets).to include widgets[:widget_b]
55
+ expect(ui.widget_list.widgets).to include widgets[:widget_c]
387
56
 
57
+ ui.delete_widget(:widget_b)
58
+ expect(ui.widget_list.widgets).to include widgets[:widget_a]
59
+ expect(ui.widget_list.widgets).to include widgets[:widget_c]
60
+ expect(ui.widget_list.widgets).to_not include widgets.deleted[:widget_b]
61
+ end
388
62
 
389
- # As above, these are equivalent, but the second line is preferred.
390
- ui.widget_list.widgets.should == k.get_data(:widgets).values_at(:widget_a, :widget_b, :widget_c)
391
- ui.widget_list.widgets.should match_mental_model_of(:widgets)
63
+ it "runs the tests against the applications's API" do
64
+ expect(api.widgets).to include widgets[:widget_a]
65
+ expect(api.widgets).to include widgets[:widget_b]
392
66
 
393
- ui.delete_widget(:widget_b)
67
+ api.create_widget(:widget_c, :name => 'Bar')
68
+ expect(api.widgets).to include widgets[:widget_a]
69
+ expect(api.widgets).to include widgets[:widget_b]
70
+ expect(api.widgets).to include widgets[:widget_c]
394
71
 
395
- # As above, these are equivalent, but the second line is preferred.
396
- ui.widget_list.widgets.should == k.get_data(:widgets).values_at(:widget_a, :widget_c)
397
- ui.widget_list.widgets.should match_mental_model_of(:widgets)
72
+ api.delete_widget(:widget_b)
73
+ expect(api.widgets).to include widgets[:widget_a]
74
+ expect(api.widgets).to include widgets[:widget_c]
75
+ expect(api.widgets).to_not include widgets.deleted[:widget_b]
398
76
  end
399
77
 
400
78
  it "catches errors based on the server error detection handler" do