Hello again Myron :)
I'm guessing that's why I couldn't find many before(:all) examples online.
I wasn't aware of aggregate_failures, that's pretty cool feature! I always
tried to follow the 'single assertion per test' principle. However, the
example above is small portion of a bigger Spec. In the same Spec file I
will also try to delete an entity (which require me to create an entity
first), get a list of entities (create at least 2 entities) and expect
exception to get raised when I'm trying to create an entity that I already
created. All of those require a working schema. I can't group all up them
under a single aggregate_failures test. So sadly, I will have to use
instance variable, or to wrap a set block like the example you shared above
(unless you have another idea)
But I'm wondering, let's say I could control every point in the process and
design this whole spec from scratch, having the option to do whatever I
want on each of the remote micro-services. What would be the proper way of
addressing it? Doesn't feel like the tests above are something specials,
more like a standard REST service testing. Here's the option I can think
about:
1. Create the schema over and over each test with 'before' block like so :
let(schema_name) { SecureRandom.uuid.to }
before
user_schema.create_schema(schema_name)
end
it 'my test' do
puts schema_name
end
This will be slower as schema will getting created every-test, and I only
really need a single one as the schema is just a dependency and no the main
focus of the test. But - perhaps clearer to read and avoid sharing context
with before(:all).
2. Pre-populate the remote test database with default user schema with
default name. I can either create it directly on the database when I spin
the environment or from spec_helper . I can even store the default values
in YML file, create an object from it (config) and use it on test.
payload = JSON.parse(File.read('spec/acceptence/fixtures/feature.json'))
payload['schema_name'] = config(:default_schema_name)
This however makes the test less clear because not all information is being
exposed when you read just the spec file. I would like to do it the
'proper' way - as I can probably talk with the other teams in the future -
makes those test better align with the standards.
Thanks for the help again!
On Tuesday, August 1, 2017 at 12:48:45 PM UTC+3, Myron Marston wrote:
>
> before(:all) hooks require special care and we usually recommend you
> avoid them. Most RSpec tooling assumes a per-example lifecycle for
> resources (such as DB transactions, test doubles, and let memoization),
> and before(:all) hooks operate outside that lifecycle. If you’re not
> careful to manage the resources explicitly you’re likely to experience
> problems from using :all hooks. For this reason we don’t provide
> syntactic sugar for them (such as a let construct), but it’s pretty
> trivial to create your own construct if you want:
>
> module BeforeAllConstructs
> def set(name, &block)
> attr_reader name
> before(:context) { instance_variable_set("@#{name}", block.call }
> endend
> RSpec.configure do |config|
> config.extend BeforeAllConstructsend
>
> With this in place, you could use set in place of let to have a construct
> like let designed for :all/:context hooks.
>
> That said, in your case, I wouldn’t recommend you take that route. It
> doesn’t seem like having two separate examples here provides you any clear
> benefit, and the fact you are considering using a before(:all) hook
> indicates you want to do some computation once, and then make multiple
> assertions about that. Instead, I’d recommend you combine the two examples,
> but then use :aggregate_failures so that you get a list of all failures
> (and not just the first one). Here’s how you could do that:
>
> RSpec.describe 'Authentication' do
> subject { AuthenticationService.new }
> let(:user_schema) { SchemaService.new }
>
> context 'When creating a user that does not exists ' do
> it 'creates a new user', :aggregate_failures do
> schema_name = SecureRandom.uuid.to_s
> user_schema.create_schema(schema_name)
> payload = JSON.parse(File.read('spec/acceptence/fixtures/feature.json'))
> payload['schema_name'] = schema_name
>
> response = subject.create_user(user: 'my_user', payload: payload)
>
> expect(response.code).to eq 200
>
> response = subject.get_user_by_category(category: payload['category'])
> remote_entity = JSON.parse(response.body)
>
> expect(payload.to_json).to eq(
> remote_entity['list'][unique_value]
> )
> end
> endend
>
> HTH,
> Myron
>
>
> On Tue, Aug 1, 2017 at 2:49 PM, Jon Gordon <[email protected] <javascript:>
> > wrote:
>
>> Refactor how? the system consists of 4 micro-services. In a single sanity
>> scenario, they are all being called (some sort of a smoke test to verify
>> all can communicate with each other when spun up). The database is being
>> written on the last micro-service in line, it's code that being handled by
>> another group. I can ask them to give me an API to mock the db, but that
>> still requires me during setup to access the mock, name the entity and then
>> use that name in the example block. As I don't communicate here with
>> objects like I do with unit-testings (instead I'm using REST API) I see no
>> way how I can achieve that?
>>
>> Thanks!
>>
>> On Tuesday, August 1, 2017 at 9:25:40 AM UTC+3, Jon Rowe wrote:
>>>
>>> > As it not allowed by RSpec to use a let value inside a before(:all)
>>> block.
>>>
>>> This is for good reason as it’s a bad idea to share test stare, you
>>> could assign a constant but it would be better to refactor your code base
>>> not to depend on an external db for each test like this.
>>>
>>> Jon Rowe
>>> ---------------------------
>>> [email protected]
>>> jonrowe.co.uk
>>>
>>> On Tuesday, 1 August 2017 at 01:39, Jon Gordon wrote:
>>>
>>> Hi again :-)
>>>
>>> So I tried to write couple of RSpec test since we last talked, I'll
>>> mention again I'm writing couple end to end tests to verify all
>>> micro-services are up and running. Please consider the following example.
>>> It's an authentication service, that can create users. Before user is being
>>> created, a schema for the user-type needs to be created on a different
>>> micro-service:
>>>
>>> RSpec.describe 'Authentication' do
>>> subject { AuthenticationService.new }
>>> let(:user_schema) { SchemaService.new }
>>>
>>> context 'When creating a user that does not exists ' do
>>> it 'response with status code 200 (success)' do
>>> schema_name = SecureRandom.uuid.to_s
>>> user_schema.create_schema(schema_name)
>>> payload = JSON.parse(File.read(
>>> 'spec/acceptence/fixtures/feature.json'))
>>> payload['schema_name'] = schema_name
>>>
>>> response = subject.create_user(user: 'my_user', payload: payload)
>>>
>>> expect(response.code).to eq 200
>>> end
>>>
>>> it 'creates a new user' do
>>> schema_name = SecureRandom.uuid.to_s
>>> user_schema.create_schema(schema_name)
>>> payload = JSON.parse(File.read(
>>> 'spec/acceptence/fixtures/feature.json'))
>>> payload['schema_name'] = schema_name
>>>
>>> subject.create_user(user: 'my_user', payload: payload)
>>>
>>> response = subject.get_user_by_category(category: payload[
>>> 'category'])
>>> remote_entity = JSON.parse(response.body)
>>>
>>> expect(payload.to_json).to eq(
>>> remote_entity['list'][unique_value]
>>> )
>>> end
>>> end
>>> end
>>>
>>> To keep it dry, I should be moving the whole schema creation into a
>>> before block:
>>>
>>> before
>>> schema_name = SecureRandom.uuid.to
>>> user_schema.create_schema(schema_name)
>>> end
>>>
>>> Before makes sense to me over let here, because it's an action. However,
>>> because the schema is more of a 'pre-condition', I can create it just once,
>>> and avoid multiple schema in my database. Therefore, before(:all) seems
>>> like a better option.
>>>
>>> before(:all)
>>> schema_name = SecureRandom.uuid.to_s
>>> user_schema.create_schema(schema_name)
>>> end
>>>
>>> Now that problem is that when I create user in my examples, I NEED to
>>> schema name, so I need to share context between the before and it block. It
>>> makes sense for me to do it like so:
>>>
>>> let(:schema_name) { SecureRandom.uuid.to_s }
>>>
>>> before(:all)
>>> user_schema.create_schema(schema_name)
>>> end
>>>
>>> Then I can create easily create user in my example like so:
>>>
>>> payload = JSON.parse(File.read('spec/acceptence/fixtures/feature.json'))
>>> payload['schema_name'] = schema_name
>>>
>>> subject.create_user(user: 'my_user', payload: payload)
>>>
>>> Alas, this will not work. As it not allowed by RSpec to use a let value
>>> inside a before(:all) block. So I need to hold a string that can be used in
>>> both the it and before block. It can solved it by defining a Constant or
>>> using Instance variable but both methods feels reek to me. I mentioned I
>>> don't have access to the database (as those are remote machines and they
>>> don't expose the ip for that database) so I can't truncate the db
>>> information and avoid the before block here.
>>>
>>> Thanks!
>>>
>>> On Thursday, July 20, 2017 at 10:57:05 AM UTC+3, Jon Gordon wrote:
>>>
>>> Not in Unit-tests of-course, but It seems like the only option in real
>>> end-to-end testing. The system is quite complex, as it's basically a set of
>>> couple micro-services. Each has it's own unique database (Can be Postgress,
>>> Casanda, Oracle...). In a single End to End test, all databases are
>>> populated with information. Clearing tables between each test can take
>>> time, and is quite complex. The CI process does re-start the containers at
>>> the very start of the test-run (so it's like restarting them to a fresh
>>> state), but not during tests.
>>>
>>> if I'll take the list of music instruments in the Faker gem for
>>> example, It only has around 10 options. So even if I use the unique flag -
>>> It will run out of options after 10 test-cases. I guess use 'msuic
>>> instruments' in one spec file, and 'cat-names' on the other to avoid it,
>>> but that means I need to 'remember' what pool of string I already used in
>>> previous tests, and that's feel even worse for me.
>>>
>>> Thanks.
>>>
>>>
>>>
>>> but I thought that there no better way around it. Because
>>>
>>> On Thursday, July 20, 2017 at 3:29:25 AM UTC+3, Myron Marston wrote:
>>>
>>> In general, if you need absolutely unique strings, `SecureRandom.uuid`
>>> is a good way to get one. UUIDs are universally unique, after all :).
>>>
>>> That said, the fact that you are running out of unique random strings
>>> from faker is concerning. All tests should work off of a "clean slate" in
>>> your database, either by wrapping each test in a rolled-back transaction,
>>> or by truncating your DB tables. Are you "leaking" DB records between
>>> tests?
>>>
>>> Myron
>>>
>>> On Wed, Jul 19, 2017 at 12:42 PM, Jon Gordon <[email protected]> wrote:
>>>
>>> Hi Myron,
>>>
>>> I will definitely check the book - looks like it's perfect for RSpec
>>> beginner. Also, thanks for sharing the example online - that's alone can
>>> give me a good starting base :)
>>>
>>> I will ask another question while posting - for unit-testing I'm using
>>> Faker gem to fake common strings. However, in end to end tests, we work
>>> against a database - so even if I'm using the 'unique' method, I'm running
>>> out of unique strings after a while. I'm using 'securerandom' to generate
>>> random number, but wondering if there's a better approach for that.
>>>
>>> Thanks!
>>>
>>> On Wednesday, July 19, 2017 at 6:33:49 PM UTC+3, Myron Marston wrote:
>>>
>>> My upcoming book, Effective Testing with RSpec 3
>>> <https://www.google.com/url?q=https%3A%2F%2Fpragprog.com%2Fbook%2Frspec3%2Feffective-testing-with-rspec-3&sa=D&sntz=1&usg=AFQjCNHGLaAn9OUSvszwbNhLSkP9Ypy-7A>,
>>>
>>> has an example of building a JSON API using end-to-end acceptance tests,
>>> isolated unit tests, and integration tests. It might fit what you're
>>> looking for better since you mentioned you're looking for examples of
>>> end-to-end testing of REST services.
>>>
>>> The code for the book is all online <https://github.com/rspec-3-book>,
>>> as well.
>>>
>>> All that said, Xavier's screen cast is very good, and I definitely
>>> recommend it, particularly if you do better with videos than printed
>>> materials.
>>>
>>> Myron
>>>
>>> On Wed, Jul 19, 2017 at 4:30 AM, Jon Gordon <[email protected]> wrote:
>>>
>>> Thanks Xavier :)
>>> I will be checking this course!
>>>
>>> Is there perhaps an open-source project with end-to-end spec tests you
>>> can recommend (REST tests are preferred, not Capybara)? something to get a
>>> reference from?
>>> Thank you.
>>>
>>> On Wednesday, July 19, 2017 at 2:24:46 AM UTC+3, Xavier Shay wrote:
>>>
>>> Obligatory plug for
>>> https://www.pluralsight.com/courses/rspec-ruby-application-testing which
>>> touches on some of the themes you're asking about :)
>>>
>>>
>>> On Tue, Jul 18, 2017, at 04:06 PM, Jon Rowe wrote:
>>>
>>> Hi Jon
>>>
>>> A couple of tips, firstly you can stub out your external dependencies
>>> for an end to end test, it just depends on the level of integration you
>>> want, it’s equally fine to do what you propose. For injecting your endpoint
>>> (IP, hostname or otherwise) you have a couple of ways of doing it, the
>>> simplest is to use environment variables e.g. `ENV[‘API_ENDPOINT’]`, or you
>>> can build yourself a config system like you mention. The reason why you
>>> don’t see big projects using external configuration files is it is usually
>>> done at the app level rather than in rspec.
>>>
>>> If you chose to go down the config file route, xml, yml or otherwise,
>>> you’d be better off loading it in a spec_helper or other such support file,
>>> and assigning it somewhere.
>>>
>>> Personally I would go with json fixture files for static json, or a
>>> generator method if it needs to be dynamic.
>>>
>>> Cheers.
>>> Jon
>>>
>>> Jon Rowe
>>> ---------------------------
>>> [email protected]
>>> jonrowe.co.uk
>>>
>>> On Wednesday, 19 July 2017 at 01:52, Jon Gordon wrote:
>>>
>>>
>>> Hi everyone,
>>>
>>> I'm quite new to RSpec, and I have used it mainly for unit-testing.
>>> Lately, a need for a small number of end-to-end tests became relevant. When
>>> writing test-cases, I'm trying to stub all dependencies, but because that's
>>> not an option when doing integration tests, I need some help to understand
>>> what's the proper way to do things. Here's couple of questions:
>>>
>>> 1. The test requires an IP for remote machine (which is not local and
>>> sadly can not be). Obviously, I shouldn't supply the IP inside the spec
>>> file. The simple way is reading an external YML file with the IP (that will
>>> get created automatically during the CI process with the right IP for
>>> example) and populate the IP directly from it. But, I was checking couple
>>> of big project that uses rspec, and I never seen an external configuration
>>> file, so I'm thinking perhaps there is a better way of doing it
>>>
>>> 2. If indeed YML file is the right answer, I'm not sure if reading from
>>> the YML file every spec file (that uses this service) is the right thing to
>>> do? Shouldn't I be using hooks instead for that?
>>>
>>> 3. The test-object is a REST service, and some of the requests require
>>> big json object. I have two options:
>>> a. I can create the json object in the spec file itself (which makes
>>> all information visible to you from the spec file itself, but clutters the
>>> spec)
>>> b. Creating an external default fixture (which is basically a json
>>> file), read from it during the spec, and re-write the values that are
>>> relevant for the specific tests.
>>>
>>> Thank you!
>>>
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "rspec" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to [email protected].
>>> To post to this group, send email to [email protected].
>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/rspec/61ac9ade-1045-4211-80d3-441ef01ae7cb%40googlegroups.com
>>>
>>> <https://groups.google.com/d/msgid/rspec/61ac9ade-1045-4211-80d3-441ef01ae7cb%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>>
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "rspec" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to [email protected].
>>> To post to this group, send email to [email protected].
>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/rspec/3FF6FCF2018A482CBDC70C02BAFFB643%40jonrowe.co.uk
>>>
>>> <https://groups.google.com/d/msgid/rspec/3FF6FCF2018A482CBDC70C02BAFFB643%40jonrowe.co.uk?utm_medium=email&utm_source=footer>
>>> .
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "rspec" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to [email protected].
>>> To post to this group, send email to [email protected].
>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/rspec/28f3f239-1515-437b-b011-82b2dd163502%40googlegroups.com
>>>
>>> <https://groups.google.com/d/msgid/rspec/28f3f239-1515-437b-b011-82b2dd163502%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "rspec" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to [email protected].
>>> To post to this group, send email to [email protected].
>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/rspec/c297c4c9-5225-47d9-a6e2-80f461bd1226%40googlegroups.com
>>>
>>> <https://groups.google.com/d/msgid/rspec/c297c4c9-5225-47d9-a6e2-80f461bd1226%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "rspec" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to [email protected].
>>> To post to this group, send email to [email protected].
>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/rspec/48d88387-8e71-49a5-b25a-850a79fe4181%40googlegroups.com
>>>
>>> <https://groups.google.com/d/msgid/rspec/48d88387-8e71-49a5-b25a-850a79fe4181%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>>
>>> --
>> You received this message because you are subscribed to the Google Groups
>> "rspec" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to [email protected] <javascript:>.
>> To post to this group, send email to [email protected] <javascript:>
>> .
>> To view this discussion on the web visit
>> https://groups.google.com/d/msgid/rspec/3ae74ab4-f4fd-4f17-b87f-4256656b57d4%40googlegroups.com
>>
>> <https://groups.google.com/d/msgid/rspec/3ae74ab4-f4fd-4f17-b87f-4256656b57d4%40googlegroups.com?utm_medium=email&utm_source=footer>
>> .
>>
>> For more options, visit https://groups.google.com/d/optout.
>>
>
>
--
You received this message because you are subscribed to the Google Groups
"rspec" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/rspec/adfd947f-875e-47cf-91e5-a95264f7aedc%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.