-
-
Notifications
You must be signed in to change notification settings - Fork 118
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
Implicit block expectation syntax #76
Comments
And this is a good thing. When testing for side effects a subject should not be cached
The code is for humans, not for machines. Right?
I'd prefer to have evidence of that. I've seen a lot of such code. And this suggestion not aligned with current best practices http://www.betterspecs.org/#subject |
Why? Not memoizing something that is memoized according to the documentation is at least confusing.
It is indeed. Humans will suffer from the same lookup problem as machines.
Sure, here you go, no usages found in
Can you please explain in more detail how |
That is not a problem. Since subject is a lambda. The lambda itself is memoized, but the result of its evaluation is not.
Only 12 of 62 real word ruby app uses RSpec. Only 5 of 12 uses
No need to repeat I don't mind about having such a cop, but I don't like an idea to enforce personal preferences via rubocop. |
Personally, when I want to test a side effect I usually do something like def perform
# ...
end and then do any of expect { perform }.to raise_error(...)
expect { perform }.to change { ... }.by(n)
# or just invoke it and have an expect after
whatever_setup
perform
expect(some_state).to be(true) or something similar. I would find the memoized lambda expect syntax very surprising if I ran across it. I've never seen it used before on projects I've worked on, but my main opinion against it is just that I don't find That said, because this seems to be rare, I'm not sure it definitely deserves specific guidance. Re:
Probably large swaths of rubocop-* and the associated style guides could easily be classified as personal preferences. There are lots of things that would be about similarly good either way, but there's benefit for project and community homogeneity for readability. I don't know if this is one of those cases, but I don't know if we can really separate out personal preferences from guides in practice. |
@dgollahon Agree to some extent. However, there's a guideline that recommends Flip-flops, and I bet nobody has seen it in the wild. What inclines me to propose this guideline is that this is a very surprising syntax even for seasoned developers, and the fact that it may actually be evaluated twice is far from being obvious. @bolshakov I have a déjà vu, I think we already had this discussion.
Cons are from above. There is no single example in RSpec repositories how to use this syntax, and documentation never mentions it.
@JonRowe about one-liner syntax is general:
Also:
With that in mind, Do you think the pros overweight cons? |
Woah, that's interesting. That strikes me as a relatively strong argument against. |
Here's some more explanation for anyone who is confused. There are two kinds of expectations. Let's call them "value expectations" and "block expectations". Certain matchers only work with one or the other. For example expect(x).to be_an(Integer) # value: works
expect { x }.to be_an(Integer) # block: ~>
# You must pass an argument rather than a block to `expect` to use the
# provided matcher (be a kind of Integer), or the matcher must implement
# `supports_block_expectations?`.
expect { x }.to raise_error(Whatever) # block: works
expect(x).to raise_error(Whatever) # value ~>
# expected Exception but was not given a block But when subject { -> { raise "hello" } }
it { is_expected.to be_a(Proc) } # works
it { is_expected.to raise_error("hello") } # works |
I'd be curious to hear some examples of when it's semantically appropriate for the |
Not necessarily in the top-level With the explicitly defined subject, I'm not aware of a commonly used practice how should the subject relate to the described class. |
Wow, you know your stuff. I stand corrected; the official docs contain an example of a spec on |
That's a different case actually, it's not a lambda, the value of the |
Implicit syntax is discouraged to use by RSpec Core team and the majority of voters. There were no good arguments for using the syntax except for brevity and avoiding repetition, but there are better options to achieve the same goal, e.g. by extracting the lengthy block to methods (instead of putting it inside the lambda). Fixes #76 https://www.reddit.com/r/ruby/comments/cejl3q/call_for_discussion_rspec_implicit_block/ https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html #76
Implicit syntax is discouraged to use by RSpec Core team and the majority of voters. There were no good arguments for using the syntax except for brevity and avoiding repetition, but there are better options to achieve the same goal, e.g. by extracting the lengthy block to methods (instead of putting it inside the lambda). Fixes #76 https://www.reddit.com/r/ruby/comments/cejl3q/call_for_discussion_rspec_implicit_block/ https://blog.rubystyle.guide/rspec/2019/07/17/rspec-implicit-block-syntax.html #76
Yet another confirmation directly from
|
I'm sorry I'm late to this conversation, again; I just came across this change with the most recent RSpec release. I appreciate this matter appears settled, but I have to disagree with the outcome. Most practically, I work on multiple projects that have literally thousands of examples written with block subjects. Even the simplest change to work around this deprecation is hours of time I just don't have. More academically, as @tomdalling pointed out in his helpful post above, there are two different types of expectations; this is because there are two different types of methods in object oriented software: methods that returns a value, and methods that have a side effect. Yes, a method can do both (e.g. One of the lovely things about block subject syntax is that it succinctly makes a clear distinction between methods meant to return a value, and methods meant to have a side effect. I use RSpec for documentation as much as for executable examples, and I can see from the first two lines of any well-written describe block what sort of method I'm looking at: describe("#wibble") do
subject { -> { thing.wibble } } # => side effect
... In fact, I see the elision of the return value from the block as a good thing: you can't cheat and add a test for the return value (thus violating SRP by having a method with a side effect and a return value). I would argue (a la David West) that methods with side-effects are preferable to methods that return values (behavior over state); they're certainly almost always more complex and interesting, and therefor more important to write clear specs for. I appreciate the technical reasoning that has gone on in this thread regarding why block subjects are to be avoided, but I would sincerely ask you to consider replacing this functionality with something equally powerful and expressive, rather than simply deprecating it. I get that examples for side effects can be replicated thusly: describe("#wibble") do
subject { -> { thing.wibble(with, bunch, o, parameters, and, such } }
it { should do_the_dew }
# becomes:
describe("#wibble") do
def perform # or some other method name that doesn't create confusion with Sidekiq
thing.wibble(with, bunch, o, parameters, and such)
end
it { expect { perform }.to do_the_dew } But, the second version certainly does not read any more clearly than the first. One of RSpec's great strengths is the readability (and thus usefulness as documentation) of the examples. This is more typing for a less clear result. And raw methods hardly fit the aesthetic profile of an RSpec spec. In addition, adding raw methods in RSpec describe/context blocks can be a quick trip to Crazy Town, not to mention Test Pollution Town, for anyone not familiar with how the internals of RSpec work. If you must remove this capability, would you consider replacing it with a distinct method? E.g.: describe("#blorf") do
subject { thing.blorf }
...
describe("#wibble") do
subject_action { -> { thing.wibble } } A Rubocop cop could autocorrect that reasonably easily, which would provide a lifeline for folks who do use this. Regarding the assertion that real-world projects don't use this syntax, I've been using RSpec extensively for fifteen years now, including on dozens of Rails projects at Pivotal Labs. I have no idea how many examples I've personally written, or helped write, but I'm sure it's well into six figures, many of which use this mechanism. I've worked with many people who test at least as extensively, many of whom use this mechanism. I fully appreciate that some people may entirely reasonably not like it, or find it intuitive,, but it is entirely possible to use it effectively on large-scale projects with large, complex spec suites. |
If it's your personal preference, I'd suggest you to define a helper method def it_should
expect { subject }
end and use it as it_should.do_the_dew |
To be honest, my personal preference is that you please not eliminate a perfectly effective and useful testing strategy. I understand the concerns with caching the subject, and I get that it's a perfectly valid opinion to not like the mechanism. But, I feel it's also valid to prefer this mechanism. As with many rubocop style choices you could default to raising an error, but allow users to opt in to allowing it. Let's keep the tent as big as possible. |
This style guide is not affiliated with RSpec. Yet another random thought considering the example you mentioned subject { -> { thing.wibble(with, bunch, o, parameters, and, such } }
it { should do_the_dew } do you think the following would be acceptable in terms for readability? subject_with_side_effects(:wibble!) { thing.wibble(with, bunch, o, parameters, and, such }
it { does the_dew }
# or
it { wibble!.to do_the_dew } It's easy to implement |
The following syntax is possible
which is the same as:
but the former comes with a few drawbacks:
subject
is not cachedBoth examples below will pass:
Keeping in mind that:
should
/is_expected
) except in some cases,There seem to be not much usage cases left for implicit block expectation syntax.
What do you think of recommending against this syntax?
Related blog post on blog.rubystyle.guide, reddit comments.
The text was updated successfully, but these errors were encountered: