-
Notifications
You must be signed in to change notification settings - Fork 0
controller specs subject naming convention and context setup best practice #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,20 +76,22 @@ long_line_under_100_chars | |
| # Extra Specs Coding Style | ||
|
|
||
| - Add a blank line between `let` and `before`. | ||
| - Prefer `ModelKlass.new` over `build_stubbed` over `build` over `create` for optimal performance. | ||
| - Use the following as a rule of thumb for context setup. If you can't follow it, stop for a second and think about your object's API, maybe the problem is there. | ||
|
|
||
| ```ruby | ||
| # after describe the first is always the subject | ||
| # after describe the first is always the subject, called the same as the described method. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
and The naming convention is quite dependent of where it's implemented. I don't think it's a good idea to make the spec looks like it was generated by a bot. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also in some cases calling it after method reduces readability, especially that method alone doesn't sound good. So I'd agree with Marc, use what sounds best in specific case rather than enforce method name always.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, there might be some collision in methods' names in global namespace.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a rule of thumb as it says 2 lines above, start from this and change upon need. For
Why don't you call the method For There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. name of the class is descriptive enough in such case and call will allow to use proc as well. For merge - you do but if it reads like normal language it's much easier to understand.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this is subjective, but still a good rule of thumb. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with rule of thumb, but the part
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just read 1 line above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. :doge: didn't notice it refers to whole section 😄 |
||
| # For controller specs use get_index, get_show, patch_update, post_create, delete_destroy, etc. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The controller specs are often used as integration tests. Also, I often use the name There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for this using For cases you mentioned - you can use request specs There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @StoneFrog Sorry, I'm not sure I understand what you mean by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| describe "#foo" | ||
| subject(:foo) { bar.foo } | ||
|
|
||
| # then lets follow in the order they will be called when subject is invoked | ||
| # then "let"s follow in the order they will be called when subject is invoked | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I though this was reverted as Seb asked: I'm still not a fan for the same reason:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed with Marc and Seb on this (even though this part is already merged and it was just a grammar change 😄 )
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This probably needs more explanation then as it's coming from the most natural flow of writing specs in RSpec. You can see how the context name always reflects the setup that's done inside the block. describe "#foo" do
subject(:foo) { klass_instance.foo }
let(:klass_instance) { described_class.new(initial_arguments) }
context "when klass_instance is initialized with this" do
let(:initial_arguments) { { asdf: true } }
end
context "when klass_instance is initialized with that" do
let(:initial_arguments) { { asdf: false } }
context "when some objects in DB are present" do
let!(:some_object_1) { create :some_object, account: account_1 }
let(:account_1) { create :account }
let!(:some_object_2) { create :some_object, account: account_2 }
let(:account_2) { create :account }
end
end
endThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
for this part if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't care about described class, use it or don't. For me it's easier when renaming, which I do while working on something quite often. About the order of account and some object, the order is based on the distance of one object to another. describe "#foo" do
subject(:foo) { klass_instance.foo }
let(:klass_instance) { described_class.new(initial_arguments) }
context "when klass_instance is initialized with this" do
let(:initial_arguments) { { asdf: true } }
end
context "when klass_instance is initialized with that" do
let(:initial_arguments) { { asdf: false } }
context "when some objects in DB are present" do
let!(:some_object_1) { create :some_object, bar: bar_1 }
let!(:some_object_2) { create :some_object, bar: bar_2 }
context "when all of their bar is nil" do
let(:bar_1) { nil }
let(:bar_2) { nil }
it { is_expected.to eq "..." }
end
context "when not all of their bar is nil" do
let(:bar_1) { nil }
let(:bar_1) { true }
it { is_expected.to eq "..." }
end
end
end
endThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in case you presented I agree that's the reasonable solution, but it is because the variables you defined later were use to set up specs.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it didn't make sense in the previous example to explicitly create account. Everything is in order of dependency here, your focus is on |
||
| let(:bar) { Bar.new(baz, qux) } | ||
| let(:baz) { build :baz, cruz: cruz } | ||
| let(:cruz) { build :cruz } | ||
| let(:gux) { build :gux, cruz: cruz } | ||
|
|
||
| # let! follows with their lets | ||
| # "let!" follows with their "let"s | ||
| let!(:corge) { create :corge, grault: grault } | ||
| let(:grault) { create :grault } | ||
| let!(:garply) { create :garply, grault: grault } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sometimes, I would prefer use
build_stubbed(:model_klass, :factory_trait)instead ofModelKlass.new(attr_1: value_1, attr_2: value_2, attr_3: value_3, attr_4: value_4).The choice of readability over performance is most of the time related to the implementation context. I don't think this is something worth having in a coding style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not enough context here, this needs extra explanation that factories might still be executing some persistence-related stuff when using
build_stubbedand in that case, when the data integrity and the state of valid attributes are needed (which would be the case forbuild_stubbed),ModelKlass.newmight be a good alternative.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This says "prefer over" not "always use".
@Azdaroth is this the place for an explanation? This is a guide to follow, not a book to learn.
Let me extend this explanation for this PR though.
From AR we get
ModelKlass.new. This might have initialization callbacks, or other stuff happening that we don't want. Then we can usedouble(ModelKlass).When we also need some values set that we don't care about - probably to pass validation - then we can use
build_stubbed. Note, that association overrides in factory likeassociation_name { create :association_factory }will be run even when calling stubbed.When you use
build, associations are going to be created. Use this when you need them.And what you should use least often, is
createas its slow. The basic factory of a model must contain just enough data to pass validation and not more.Being aware of what you create in specs will make you aware of what data you're using in the code. Overusing factories can slow down our spec suite and our code as well.
If you're starting from
ModelKlass.newthen you're aware of all the data that your object is using. When it becomes hard to setup context, it can be a sign that your object's responsibility is too big, or its interface is clumsy.