From d18a3181843a6a872aad09a8f5a0538eef53cdf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Glauco=20Cust=C3=B3dio?= Date: Sat, 3 Aug 2024 21:48:58 +0100 Subject: [PATCH] add article for described class --- _config.yml | 3 ++ _includes/all_possible_cases.html | 12 ++--- _includes/avoid_hooks.html | 31 +++++++++---- _includes/create_only_the_data_you_need.html | 10 ++-- _includes/describe.html | 18 ++++++- _includes/dont_use_shared_examples.html | 16 +++---- _includes/factories_not_fixtures.html | 14 +++--- _includes/group_expectations.html | 2 +- _includes/instance_double_over_double.html | 4 +- _includes/lets_not.html | 22 +++++---- _includes/leverage_described_class.html | 49 ++++++++++++++++++++ _includes/mock_external_dependencies.html | 3 +- _includes/short_description.html | 10 ++-- _includes/use_contexts.html | 8 ++-- index.html | 1 + 15 files changed, 146 insertions(+), 57 deletions(-) create mode 100644 _includes/leverage_described_class.html diff --git a/_config.yml b/_config.yml index 663fd3d..1c67e04 100644 --- a/_config.yml +++ b/_config.yml @@ -21,6 +21,9 @@ nav_external_links: - title: Use contexts url: "#use-contexts" hide_icon: true + - title: Leverage described class + url: "#leverage-described-class" + hide_icon: true - title: Short description url: "#short-description" hide_icon: true diff --git a/_includes/all_possible_cases.html b/_includes/all_possible_cases.html index c1fb926..2b20b61 100644 --- a/_includes/all_possible_cases.html +++ b/_includes/all_possible_cases.html @@ -5,7 +5,7 @@

-

Testing is a good practice, but if you do not test the edge cases, it will not be useful. Test valid, edge and invalid case. For example, consider the following action..

+

Testing is a good practice, but if you do not test the edge cases, it will not be useful. Test valid, edge and invalid cases.

If you have way too many cases to test, it might be an indication your subject class is doing too much and must be break down into other classes.

@@ -23,8 +23,8 @@

{% highlight ruby %} -describe '#destroy' do - context 'when product exists' do +describe 'DELETE /:id' do + context 'when the product exists' do it 'deletes the product' do end end @@ -34,13 +34,13 @@

{% highlight ruby %} -describe '#destroy' do - context 'when product exists' do +describe 'DELETE /:id' do + context 'when the product exists' do it 'deletes the product' do end end - context 'when product does not exist' do + context 'when the product does not exist' do it 'raises 404' do end end diff --git a/_includes/avoid_hooks.html b/_includes/avoid_hooks.html index a1837b6..61f5660 100644 --- a/_includes/avoid_hooks.html +++ b/_includes/avoid_hooks.html @@ -5,24 +5,28 @@

-

Avoid hooks whenever possible since they tend to make your tests complicated over time.

+

Avoid hooks since they usually cause your tests to become more complex in the long run.

{% highlight ruby %} -describe '#index' do +describe 'GET /' do context 'when user is authenticated' do before do @user = create(:user) sign_in @user + get profile_path end - context 'when user is admin' do + context 'when user has a profile' do it 'returns 200' do + create(:profile, user: @user) + expect(response.code).to eq('200') end end - context 'when user is not admin' do - it 'returns 401' do + context 'when user does not have a profile' do + it 'returns 404' do + expect(response.code).to eq('404') end end end @@ -32,19 +36,28 @@

{% highlight ruby %} -describe '#index' do +describe 'GET /' do context 'when user is authenticated' do - context 'when user is admin' do + context 'when user has a profile' do it 'returns 200' do user = create(:user) + create(:profile, user: user) sign_in user + + get profile_path + + expect(response.code).to eq('200') end end - context 'when user is not admin' do - it 'returns 401' do + context 'when user does not have a profile' do + it 'returns 404' do user = create(:user) sign_in user + + get profile_path + + expect(response.code).to eq('404') end end end diff --git a/_includes/create_only_the_data_you_need.html b/_includes/create_only_the_data_you_need.html index 76d78f1..5692a31 100644 --- a/_includes/create_only_the_data_you_need.html +++ b/_includes/create_only_the_data_you_need.html @@ -9,12 +9,12 @@

{% highlight ruby %} -describe '#featured_product' do +describe '.featured_product' do it 'returns the featured product' do create_list(:product, 5) product_featured = create(:product, featured: true) - expect(subject.featured_product).to eq(product_featured) + expect(described_class.featured_product).to eq(product_featured) end end {% endhighlight %} @@ -22,12 +22,12 @@

{% highlight ruby %} -describe '#featured_product' do +describe '.featured_product' do it 'returns the featured product' do - product_non_featured = create(:product, featured: false) + create(:product, featured: false) product_featured = create(:product, featured: true) - expect(subject.featured_product).to eq(product_featured) + expect(described_class.featured_product).to eq(product_featured) end end {% endhighlight %} diff --git a/_includes/describe.html b/_includes/describe.html index 7e2f207..db05ca9 100644 --- a/_includes/describe.html +++ b/_includes/describe.html @@ -5,7 +5,7 @@

-

Be clear about what method you are describing. For instance, use the Ruby documentation convention of . when referring to a class method's name and # when referring to an instance method's name.

+

Be clear about what you are testing.

{% highlight ruby %} @@ -19,6 +19,8 @@

{% endhighlight %}

+

Use the Ruby documentation convention of . when referring to a class method's name and # when referring to an instance method's name.

+
{% highlight ruby %} describe User do @@ -28,6 +30,20 @@

describe '#admin?' do end end +{% endhighlight %} +

+ +

In request tests, describe the methods and paths under test.

+ +
+{% highlight ruby %} +describe UsersController, type: :request do + describe 'POST /' do + end + + describe 'GET /' do + end +end {% endhighlight %}
diff --git a/_includes/dont_use_shared_examples.html b/_includes/dont_use_shared_examples.html index dbe9e80..7715680 100644 --- a/_includes/dont_use_shared_examples.html +++ b/_includes/dont_use_shared_examples.html @@ -10,12 +10,11 @@

{% highlight ruby %} shared_examples 'a normal dog' do - it { is_expected.to be_able_to_jump } it { is_expected.to be_able_to_bark } end -RSpec.describe Dog do - subject { described_class.new(able_to_jump?: true, able_to_bark?: true) } +describe Dog do + subject { described_class.new(able_to_bark?: true) } it_behaves_like 'a normal dog' end {% endhighlight %} @@ -23,12 +22,13 @@

{% highlight ruby %} -RSpec.describe Dog do - it 'barks and jumps' do - subject = described_class.new(able_to_jump?: true, able_to_bark?: true) +describe Dog do + describe '#able_to_bark?' do + it 'barks' do + subject = described_class.new(able_to_bark?: true) - expect(subject).to be_able_to_jump - expect(subject).to be_able_to_bark + expect(subject.able_to_bark?).to eq(true) + end end end {% endhighlight %} diff --git a/_includes/factories_not_fixtures.html b/_includes/factories_not_fixtures.html index 50eb514..67c45ff 100644 --- a/_includes/factories_not_fixtures.html +++ b/_includes/factories_not_fixtures.html @@ -5,11 +5,11 @@

-

Factories are more flexible and easy to work with.

+

Factories are more flexible and easier to work with.

{% highlight ruby %} -def name +def full_name "#{first_name} #{last_name}" end {% endhighlight %} @@ -18,8 +18,9 @@

{% highlight ruby %} it 'returns the full name' do - user = create(:user, fist_name: 'Ayrton', last_name: 'Senna') - expect(user.name).to eq('Ayrton Senna') + user = create(:user, fist_name: 'Santos', last_name: 'Dumont') + + expect(user.full_name).to eq('Santos Dumont') end {% endhighlight %}
@@ -29,8 +30,9 @@

{% highlight ruby %} it 'returns the full name' do - user = build_stubbed(:user, fist_name: 'Ayrton', last_name: 'Senna') - expect(user.name).to eq('Ayrton Senna') + user = build_stubbed(:user, fist_name: 'Santos', last_name: 'Dumont') + + expect(user.full_name).to eq('Santos Dumont') end {% endhighlight %}
diff --git a/_includes/group_expectations.html b/_includes/group_expectations.html index ed46285..c1dd66a 100644 --- a/_includes/group_expectations.html +++ b/_includes/group_expectations.html @@ -6,7 +6,7 @@

- Having an it for each expectation can lead to a terrible test performance. Try to group expectations that + Having an it for each expectation can lead to a terrible test performance. Group expectations that use a similar data setup to improve performance and make the tests more readable.

diff --git a/_includes/instance_double_over_double.html b/_includes/instance_double_over_double.html index 7c041ee..47da8e3 100644 --- a/_includes/instance_double_over_double.html +++ b/_includes/instance_double_over_double.html @@ -20,7 +20,7 @@

{% highlight ruby %} it "passes" do - user = double(:user, name: "Ayrton Senna") + user = double(:user, name: "Gustavo Kuerten") puts user.name end {% endhighlight %} @@ -29,7 +29,7 @@

{% highlight ruby %} it "fails" do - user = instance_double(User, name: "Ayrton Senna") + user = instance_double(User, name: "Gustavo Kuerten") puts user.name end {% endhighlight %} diff --git a/_includes/lets_not.html b/_includes/lets_not.html index 1e29b46..b1d3521 100644 --- a/_includes/lets_not.html +++ b/_includes/lets_not.html @@ -5,26 +5,26 @@

-

Do not use let / let!. These tend to turn your tests very complicated over time as one needs to look up variables defined then apply deltas to figure their current state. Read more here.

+

Do not use let / let!. These tend to turn your tests very complicated over time as one needs to look up variables defined then apply deltas to figure their current state. Understand more here.

Tests are not supposed to be DRY, but easy to read and maintain.

{% highlight ruby %} -describe '#name' do - let(:user) { create(:user, fist_name: 'Ayrton', last_name: 'Senna') } +describe '#full_name' do + let(:user) { create(:user, fist_name: 'Edson', last_name: 'Pelé') } context 'when first name and last name are present' do it 'returns the full name' do - expect(user.name).to eq('Ayrton Senna') + expect(user.full_name).to eq('Edson Pelé') end end context 'when last name is not present' do it 'returns the first name' do user.last_name = nil - expect(user.name).to eq('Ayrton') + expect(user.full_name).to eq('Edson') end end end @@ -33,18 +33,20 @@

{% highlight ruby %} -describe '#name' do +describe '#full_name' do context 'when first name and last name are present' do it 'returns the full name' do - user = create(:user, fist_name: 'Ayrton', last_name: 'Senna') - expect(user.name).to eq('Ayrton Senna') + user = create(:user, fist_name: 'Edson', last_name: 'Pelé') + + expect(user.full_name).to eq('Edson Pelé') end end context 'when last name is not present' do it 'returns the first name' do - user = create(:user, fist_name: 'Ayrton', last_name: nil) - expect(user.name).to eq('Ayrton') + user = create(:user, fist_name: 'Edson', last_name: nil) + + expect(user.full_name).to eq('Edson') end end end diff --git a/_includes/leverage_described_class.html b/_includes/leverage_described_class.html new file mode 100644 index 0000000..64610ae --- /dev/null +++ b/_includes/leverage_described_class.html @@ -0,0 +1,49 @@ +
+

+ + Leverage described class + +

+ +

Tests are not supposed to be DRY, but that doesn't mean we need to repeat ourselves in vain. Leverage described_class to make your tests maintainable over the years.

+ +
+{% highlight ruby %} +describe Pilot do + describe '.most_successful' do + it 'returns the most successful pilot' do + senna = create(:pilot, name: 'Ayrton Senna') + create(:pilot, name: 'Alain Prost') + create(:race, winner: senna) + + most_successful_pilot = Pilot.most_successful + + expect(most_successful_pilot.name).to eq('Ayrton Senna') + end + end +end +{% endhighlight %} +
+ + +
+{% highlight ruby %} +describe Pilot do + describe '.most_successful' do + it 'returns the most successful pilot' do + senna = create(:pilot, name: 'Ayrton Senna') + create(:pilot, name: 'Alain Prost') + create(:race, winner: senna) + + most_successful_pilot = described_class.most_successful + + expect(most_successful_pilot.name).to eq('Ayrton Senna') + end + end +end +{% endhighlight %} +
+ + +

If the class Pilot ever gets renamed, one just need to change it at the top level describe.

+
\ No newline at end of file diff --git a/_includes/mock_external_dependencies.html b/_includes/mock_external_dependencies.html index 47dce91..7af7fc6 100644 --- a/_includes/mock_external_dependencies.html +++ b/_includes/mock_external_dependencies.html @@ -31,6 +31,7 @@

describe '#github_stars' do it 'displays the number of stars' do expect(Github).to receive(:fetch_repository_stars).with(1).and_return(10) + expect(subject.github_stars(1)).to eq('Stars: 10') end end @@ -54,7 +55,7 @@

{% highlight ruby %} describe '#process' do it 'creates a user and a product' do - payload = { user: { name: 'John' }, product: { name: 'Book' } } + payload = { user: { name: 'Isabel' }, product: { name: 'Book' } } subject.process(payload) diff --git a/_includes/short_description.html b/_includes/short_description.html index 119e3c8..254582e 100644 --- a/_includes/short_description.html +++ b/_includes/short_description.html @@ -9,23 +9,23 @@

{% highlight ruby %} -it 'returns 422 when users first_name is missing from params and when users last_name is missing from params' do +it 'returns 422 when user first_name is missing from params and when user last_name is missing from params' do end -it 'returns 422 when users first_name is missing from params \ - and when users last_name is missing from params' do +it 'returns 422 when user first_name is missing from params \ + and when user last_name is missing from params' do end {% endhighlight %}
{% highlight ruby %} -context 'when users first_name is missing from params' do +context 'when user first_name is missing from params' do it 'returns 422' do end end -context 'when users last_name is missing' do +context 'when user last_name is missing from params' do it 'returns 422' do end end diff --git a/_includes/use_contexts.html b/_includes/use_contexts.html index 38979b1..09cd464 100644 --- a/_includes/use_contexts.html +++ b/_includes/use_contexts.html @@ -5,7 +5,7 @@

-

Contexts are a powerful way to make your tests clear and well organized (they keep tests easy to read). They should start with when.

+

Contexts are a powerful way to make your tests clear and well organized. They should start with when.

{% highlight ruby %} @@ -14,7 +14,7 @@

end describe 'it returns 401 status code if not logged in' do - expect(response.code).to eq('401') + it { expect(response.code).to eq('401') } end {% endhighlight %}

@@ -22,7 +22,9 @@

{% highlight ruby %} context 'when logged in' do - expect(response.code).to eq('200') + it 'returns 200 status code' do + expect(response.code).to eq('200') + end end context 'when logged out' do diff --git a/index.html b/index.html index 15bd0ad..08e1b1c 100644 --- a/index.html +++ b/index.html @@ -28,6 +28,7 @@

{% include describe.html %} {% include use_contexts.html %} +{% include leverage_described_class.html %} {% include short_description.html %} {% include group_expectations.html %} {% include all_possible_cases.html %}