kookaburra 1.3.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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