【RSpec】Active Hashを用いたカテゴリーでテストをパスする方法

https://teratail.com/questions/275207
結論この記事

**今回は、active hashを用いたカテゴリーの際に、テストがパスしずらい問題を解決していきます**

詳しくいうと、

コントローラーの単体テストで、

```ruby
    it 'showアクションにリクエストすると,そのコーヒーの産地が表示' do
      get drink_path(@drink)
      expect(response.body).to include('マルチリージョン')
    end
```

このようなテストがパスできなかったです。

期待する動作としては、

![スクリーンショット 2021-04-15 16.17.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653881/931bd5f2-a002-a6a5-a57e-05e76e134256.png)

この画像の右部分に,
「産地 マルチリージョン」
とあるので、上記のようなテストを書きましたが、
**drinks/show.html.erb**

```
                <% if @drink.region %>
                  <th class="region-title">産地 </th>
                   <td class="region-name"><%= @drink.region.name%> </td>             
                <% end %>
```

@drinks.region.name には、「マルチリージョン」という文字が入って欲しかったのですが、

上記のテストを実行した結果、

```
 Drinks GET #show showアクションにリクエストすると,そのコーヒーの産地が表示
     Failure/Error: expect(response.body).to include('マルチリージョン')
     
       expected "\n<!DOCTYPE html>\n<html>\n  <head>\n    <title>Details | Coffee Passport</title>\n    \n    \n\n   ...script src=\"/packs-test/js/footer-c1b52f0e268f9a453b2e.js\"></script>\n   \n  </body>\n</html>\n\n" to include "マルチリージョン"
       Diff:

中略

       +              <tr class="item-region">
       +                  <th class="region-title">産地 </th>
       +                   <td class="region-name"> </td>             
       +              </tr>

```

このようなHTMLが返され、
```
   <td class="region-name"> </td>   
```
ここに @drink.region.nameが入るはずですが、
何も入ってなかったので、色々試しました。
色々試した結果は、「teratailに質問しようとした文」の部分に色々書いてあります。

# 該当のコード

**drinks/show.html.erb**

```erb
   <% if @drink.region %>
     <th class="region-title">産地 </th>
     <td class="region-name"><%= @drink.region.name%> </td>             
    <% end %>
```

**models/region.rb**

```ruby
class Region < ActiveHash::Base
  self.data = [
    { id: 1, name: '---'},
    { id: 2, name: 'マルチリージョン' },
    { id: 3, name: 'ラテンアメリカ' },
    { id: 4, name: 'アフリカ' },
    { id: 5, name: 'アジア、太平洋' }
  ]
end

```

**factories/drinks.rb**

```ruby
FactoryBot.define do
  factory :drink do
    name                {"TOKYOロースト"}
    price               {350}
    explain             {"これはコーヒーの説明です"}
    region_id           {2}
```


このようなactive hashを用いた構成の場合に、しっかり<%= @drink.region.name%> 
が表示されてるか確かめたいので、

**spec/requests/drinks_spec.rb**

```ruby
    it 'showアクションにリクエストすると,そのコーヒーの産地が表示' do
      get drink_path(@drink)
      expect(response.body).to include('マルチリージョン')
    end
```

 と、テストを書いた。

しかし、これだと、先ほど説明した通りに上手くいかない

**spec/requests/drinks_spec.rb**

```ruby
require 'rails_helper'
# bundle exec rspec spec/requests/tweets_spec.rb

RSpec.describe "Drinks", type: :request do
  before do
    @user = FactoryBot.create(:user)
    region_id = Region.find_by(name: 'マルチリージョン').id
    body_id = Body.find_by(name: "LIGHT(軽い)").id
    acidity_id = Acidity.find_by(name: "LOW(少ない)").id
    processing_id = Processing.find_by(name: "WASHED(水洗式)").id
    @drink = FactoryBot.create(:drink,
                                name: "TOKYOロースト",
                                price: 350,
                                explain: "これはコーヒーの説明です",
                                region_id: region_id,
                                body_id: body_id,
                                acidity_id: acidity_id,
                                processing_id: processing_id)
```

結論このように書けば上手くいく

**models/region.rb**

```ruby
class Region < ActiveHash::Base
  self.data = [
    { id: 1, name: '---'},
    { id: 2, name: 'マルチリージョン' },
    { id: 3, name: 'ラテンアメリカ' },
    { id: 4, name: 'アフリカ' },
    { id: 5, name: 'アジア、太平洋' }
  ]
end

```

**spec/requests/drinks_spec.rb**

```ruby
    region_id = Region.find_by(name: 'マルチリージョン').id

    @drink = FactoryBot.create(:drink,
                                name: "TOKYOロースト",
                                price: 350,
                                explain: "これはコーヒーの説明です",
                                region_id: region_id,
```

これで、うまいこと取得できて、テストをパスできる


多分これは冗長で、ベストプラクティスではないような気がするので、プロのRubistの皆さんにご意見をお伺いしたいと思うので、それを追記していきます。

# teratailに質問しようとした文

# [このアプリのgithub](https://github.com/Harasou21/coffee_passport)
# [起きてる問題点はこれに近いです](https://qiita.com/Ma_ni_/items/2beff46433c6d115e5bc)


### 発生している問題・エラーメッセージ


```
 Drinks GET #show showアクションにリクエストすると,そのコーヒーの産地が表示
     Failure/Error: expect(response.body).to include('マルチリージョン')
     
       expected "\n<!DOCTYPE html>\n<html>\n  <head>\n    <title>Details | Coffee Passport</title>\n    \n    \n\n   ...script src=\"/packs-test/js/footer-c1b52f0e268f9a453b2e.js\"></script>\n   \n  </body>\n</html>\n\n" to include "マルチリージョン"
       Diff:

中略

       +              <tr class="item-region">
       +                  <th class="region-title">産地 </th>
       +                   <td class="region-name"> </td>             
       +              </tr>

```

     <td class="region-name"> </td>   

ここに、コーヒーの産地が表示されるはずですが、テストで返されたHTMLには表示されてないので、テストがパスできません。

原因は、

![スクリーンショット 2021-04-15 16.17.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653881/931bd5f2-a002-a6a5-a57e-05e76e134256.png)


この画像の、この投稿の産地の部分の「マルチリージョン」とか 、コクの「LIGHT(軽い)」の部分とかが、上手く表示されてないです。

### 該当のソースコード



**drinks/show.html**
```
                <% if @drink.region %>
                  <th class="region-title">産地 </th>
                   <td class="region-name"><%= @drink.region.name%> </td>             
                <% end %>
```

と、こんな感じで、drinks/showでは、
その投稿の産地の名前が表示される仕様になっています

**region.rb** 

```
class Region < ActiveHash::Base
  self.data = [
    { id: 1, name: '---'},
    { id: 2, name: 'マルチリージョン' },
    { id: 3, name: 'ラテンアメリカ' },
    { id: 4, name: 'アフリカ' },
    { id: 5, name: 'アジア、太平洋' }
  ]

end
```

ここで、Active Hashを用いてることが原因となっております

**drinks_spec.rb**

```
    it 'showアクションにリクエストすると,そのコーヒーの産地が表示' do
      get drink_path(@drink)
      expect(response.body).to include('マルチリージョン')
    end
```

**spec/factories/drinks.rb**

```
FactoryBot.define do
  factory :drink do
    name                {"TOKYOロースト"}
    price               {350}
    explain             {"これはコーヒーの説明です"}
    region_id           {Region.all.sample}
    body_id             {Body.all.sample}
    acidity_id          {Acidity.all.sample}
    processing_id       {Processing.all.sample}
    likes_count         {2}
    association :user 
    association :region
    association :body
    association :acidity
    association :processing

    after(:build) do |drink|
      drink.image.attach(io: File.open('app/assets/images/ethiopia.jpg'), filename: 'ethiopia.jpg')
    end
  end
end

```

**spec/factories/regions.rb**

```
FactoryBot.define do
  factory :region do
  end
end

```

### 試したこと

https://qiita.com/Ma_ni_/items/2beff46433c6d115e5bc

https://endoakak.hatenablog.com/entry/2020/08/06/200000

この二つの記事を参考にして、

まず、factorybot同士でアソシエーションを組んでないから、@drink.region.nameが表示されないと思って、

**spec/factories/regions.rb**

```
FactoryBot.define do
  factory :region do
    association :drink
  end
end

```

と記述したら

死ぬほど長いエラーが表示され、SystemStackError stack level too deep
こんな感じのエラーが出ました。

次に

**app/models/region.rb**

```
class Region < ActiveHash::Base
  self.data = [
    { id: 1, name: '---'},
    { id: 2, name: 'マルチリージョン' },
    { id: 3, name: 'ラテンアメリカ' },
    { id: 4, name: 'アフリカ' },
    { id: 5, name: 'アジア、太平洋' }
  ]
  + include ActiveHash::Associations
   + has_many :drink
end

```

このように記述しました
一番最初のエラーと変わりませんでした。


**factories/region.rb**

```
FactoryBot.define do
  factory :region do
    2      {'マルチリージョン'}
  end
end

```

このように記述したら

```
SyntaxError:
  /coffee_passport/spec/factories/regions.rb:3: syntax error, unexpected '{', expecting end
      2      {'マルチリージョン'}
             ^
```

このようなエラーになりました。

**factores/drinks.rb**
に

```
    region_id           {Region.second}
```

このように記述しなおしたら

```
      Failure/Error: region_id           {Region.second}
      
      NoMethodError:
        undefined method `second' for Region:Class
        Did you mean?  send
```

とエラーが表示されました。