親モデルの単体テストをやったから、ついでに子モデルの画像アップロードテストもやろうとして頭を抱えた話。

#今回やること
[前回](https://qiita.com/0thefool/items/5e5935204c5a62f8bcce)、やっとまともに単体テストをやって、気を良くしたので親子関係のあるモデルの画像投稿の単体テストをやろうと思いました。
本当は「子モデルの単体テスト」だけにしたかったのですが、子モデルのカラムには画像保存のカラムしかなかった。
なので半強制的に画像の投稿テストまでやることになりました。

色々と勉強になったので備忘録として記載します。
#動作環境
ruby 2.5.1
rails 5.2.4.2
carrierwave 2.1.0
mini_magick 4.9.5
#下準備
ここまでの環境は[前々回](https://qiita.com/0thefool/items/5af0f17f0075e7e0e013)とか[前回](https://qiita.com/0thefool/items/5e5935204c5a62f8bcce)の記事の下準備をご覧ください。
#今回のテスト環境の準備
起動時間が早くなるらしいと聞いたので **'spring-commands-rspec'** を追加します。（必須ではありません。）
また、後々、データの確認に **'pry-rails'** が必要になるとお告げがあったので、これも追加しておきます。

####gemのインストール
```Gemfile.
 (以下を追記)
  gem 'spring-commands-rspec'
  gem 'pry-rails'
```
```terminal.
$ bundle install
```

####rspecファイルを作成する
```terminal.
$ bin/rails g rspec:model image
```
####テスト用インスタンスを作成する
```spec/factoreis/image.rb
FactoryBot.define do
  factory :image do
    image    {"image.jpg"} #image は 画像ファイル想定なので "image.jpg" を代入します。
    association: item 
  end
end
```
「モデル名とカラム名だけ変えて、あとは前回と同じでいいでしょ。」みたいなノリで作りました。
**※** associationを書くことで、[前回](https://qiita.com/0thefool/items/5e5935204c5a62f8bcce)の記事で作成したitemのインスタンスをitem_idに入れてくれるそうです。（便利）
####テストコードを記述
```spec/models/image_spec.rb
require 'rails_helper'

RSpec.describe Image, type: :model do
  describe '#create' do
    let(:image) {build(:image)}
    context 'can save' do
      it "is valid with a image,item_id" do #image に "image.jpg" が入っていて、item_idはassociationで補うため
        expect(image).to be_valid           #成功する(はず)
      end
    end
    context 'can not save' do
      it "is invalid without a image" do
        image.image = ""            #image が 空の場合
        expect(item).to be_invalid  #失敗する（はず）
      end
      it "is invalid without a item_id" do
        image.item_id = ""          #item_id が 空の場合
        expect(item).to be_invalid  #失敗する（はず）
      end
    end
  end
end
```
「モデル名とカラム名だけ変えて、あとは前回と大体同じでいいでしょ。」みたいなノリで(以下略)

####忘れずにvalidationを記述
```app/models/image.rb
class Image < ApplicationRecord
  (中略)
  validates :image,:item_id,presence: true
end
```
かろうじて思い出した。

#テストの実行結果
「前回の失敗踏まえてvalidationも書いたし、一発でテスト完了するまであるのでは？」と思いながら、いざ、実行。

```terminal.
$ bundle exec rspec spec/models/image_spec.rb
```
```結果.
Image
  #create
    can save
      is valid with a image,item_id (FAILED - 1)
    can not save
      is invalid without a image
      is invalid without a item_id

Failures:

  1) Image#create can save is valid with a image,item_id
     Failure/Error: expect(image).to be_valid
       expected #<Image id: nil, item_id: nil, image: nil, created_at: nil, updated_at: nil> to be valid, but got errors: Image can't be blank, Item can't be blank
     # ./spec/models/image_spec.rb:8:in `block (4 levels) in <top (required)>'

Finished in 0.40532 seconds (files took 5.77 seconds to load)
3 examples, 1 failure

Failed examples:

rspec ./spec/models/image_spec.rb:7 # Image#create can save is valid with a image,item_id
```

**全然ダメでした。**

#原因を探る
ただ情報は落ちました。
```but got errors: Image can't be blank, Item can't be blank```
曰く、「```image```と```item```が```nil```なんで作れません。」とのこと。

**は？ インスタンスちゃんと作ったよね？**
```binding．pry```を記述して確認してみる。

お告げに従って```'pry-rails'```を入れておいて良かった！

```spec/models/image_spec.rb
   (中略)
    context 'can save' do
      it "is valid with a image,item_id" do
        binding.pry
        expect(image).to be_valid
      end
    end
   (後略)
```
```terminal.
$ bundle exec rspec spec/models/image_spec.rb
```
```binding.pry
     3: RSpec.describe Image, type: :model do
     4:   describe '#create' do
     5:     let(:image) {build(:image)}
     6:     context 'can save' do
     7:       it "is valid with a image,item_id" do
 =>  8:         binding.pry
     9:         expect(image).to be_valid
    10:       end
    11:     end
    12:     context 'can not save' do
    13:       it "is invalid without a image" do

[1] pry(#<RSpec::ExampleGroups::Image::Create::CanSave>)> image
=> #<Image:0x00007fecd2c898c8 id: nil, item_id: nil, image: nil, created_at: nil, updated_at: nil>
```
確かに、```item_id```も```image```も```nil```です。

#Item_idを作る
調べたところ、どうも```build```では、```item_id```を生成してくれないようです。
そこで```build```を```id```を生成してくれるらしい```build_stubbed```へ変更します。

```spec/models/image_spec.rb
   (中略)
    let(:image) {build_stubbed(:image)}
    context 'can save' do
      it "is valid with a image,item_id" do
        binding.pry
        expect(image).to be_valid
      end
    end
   (後略)
```
```terminal.
$ bundle exec rspec spec/models/image_spec.rb
```
```binding.pry
     3: RSpec.describe Image, type: :model do
     4:   describe '#create' do
     5:     let(:image) {build_stubbed(:image)}
     6:     context 'can save' do
     7:       it "is valid with a image,item_id" do
 =>  8:         binding.pry
     9:         expect(image).to be_valid
    10:       end
    11:     end
    12:     context 'can not save' do
    13:       it "is invalid without a image" do

[1] pry(#<RSpec::ExampleGroups::Image::Create::CanSave>)> image
=> #<Image:0x00007fd9f98b7510 id: 1002, item_id: 1001, image: nil, created_at: Fri, 01 May 2020 13:05:44 UTC +00:00, updated_at: Fri, 01 May 2020 13:05:44 UTC +00:00>
```
```結果.
Image
  #create
    can save
      is valid with a image,item_id (FAILED - 1)
    can not save
      is invalid without a image
      is invalid without a item_id

Failures:

  1) Image#create can save is invalid with a image,item_id
     Failure/Error: expect(image).to be_valid
       expected #<Image id: 1002, item_id: 1001, image: nil, created_at: "2020-05-01 13:09:02", updated_at: "2020-05-01 13:09:02"> to be valid, but got errors: Image can't be blank
     # ./spec/models/image_spec.rb:8:in `block (4 levels) in <top (required)>'

Finished in 0.17615 seconds (files took 2.58 seconds to load)
3 examples, 1 failure

Failed examples:

rspec ./spec/models/image_spec.rb:7 # Image#create can save is valid with a image,item_id
```

ちゃんと```item_id```が作成され、エラーも```but got errors: Image can't be blank```のみになりました。

#インスタンスに画像ファイルを代入する。
どうもファイル名をstring型で与えるだけでは、```carrierwave```の```validation```に引っかかって```nil```になってしまうようです。
```image```へファイルを代入するには以下へコードを書き換える必要がありました。

```spec/factoreis/image.rb
FactoryBot.define do
  factory :image do
    image    { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/image.jpg'))}
    #記載したpathに画像ファイルを配置する必要あります。（ここでは'spec/fixtures/image.jpg'）
    association :item
  end
end
```
```terminal.
$ bundle exec rspec spec/models/image_spec.rb
```
```binding.pry
     3: RSpec.describe Image, type: :model do
     4:   describe '#create' do
     5:     let(:image) {build_stubbed(:image)}
     6:     context 'can save' do
     7:       it "is valid with a image,item_id" do
 =>  8:         binding.pry
     9:         expect(image).to be_valid
    10:       end
    11:     end
    12:     context 'can not save' do
    13:       it "is valid without a image" do

[1] pry(#<RSpec::ExampleGroups::Image::Create::CanSave>)> image
=> #<Image:0x00007f9c93dbd6e8 id: 1002, item_id: 1001, image: nil, created_at: Fri, 01 May 2020 13:22:21 UTC +00:00, updated_at: Fri, 01 May 2020 13:22:21 UTC +00:00>
```
あれ？ ```image: nil```?
念のため、```image.image```で```parameter```を確認します。

```binding.pry
[2] pry(#<RSpec::ExampleGroups::Image::Create::CanSave>)> image.image
=> #<ImageUploader:0x00007f9c8fab4748
 @cache_id="1588339341-125220018744846-0001-9913",
 @cache_storage=#<CarrierWave::Storage::File:0x00007f9c94889c70 @cache_called=nil, @uploader=#<ImageUploader:0x00007f9c8fab4748 ...>>,
 @file=
  #<CarrierWave::SanitizedFile:0x00007f9c93d83128
   @content=nil,
   @content_type="text/plain",
   @file="/Users/sggk.mrsk/projects/frima_sample/public/uploads/tmp/1588339341-125220018744846-0001-9913/test.jpg",
   @original_filename="test.jpg">,
 @filename="test.jpg",
 @identifier=nil,
 @model=#<Image:0x00007f9c93dbd6e8 id: 1002, item_id: 1001, image: nil, created_at: Fri, 01 May 2020 13:22:21 UTC +00:00, updated_at: Fri, 01 May 2020 13:22:21 UTC +00:00>,
 @mounted_as=:image,
 @original_filename="test.jpg",
 @staged=true,
 @versions={}>
```
ホッ、よかった。```image: nil```は見せかけでした。

#結果
```terminal.
$ bundle exec rspec spec/models/image_spec.rb
```
```結果.
Image
  #create
    can save
      is valid with a image,item_id
    can not save
      is valid without a image
      is valid without a item_id

Finished in 0.68652 seconds (files took 5.75 seconds to load)
3 examples, 0 failures
```
**なんとか成功しました！**

#教訓
テストって奥が深い。

今回は以上です。

#参考にさせていただいた記事
- [FactoryGirlのbuildとbuild_stubbedって何が違うの？](https://qiita.com/IKEA_dless/items/dff64c5cbc320c1369de)
- [画像アップロード CarrierWaveを使ったmodelのRSpecを通す](https://qiita.com/motty93/items/5cbc46ce79a502fcaa65)
- [carrierwaveuploader/carrierwave/wiki](https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Use-test-factories)