【vue.js入門 基礎から実践アプリケーション開発まで】を読みながら勉強中。でも進捗が3日無いのでムシャクシャしてます

7 min read読了の目安(約6600字

Vue.jsの勉強を始めました

最近Web系の勉強を始めた初心者の記事です。おかしなことを書いてる可能性があるので生暖かい目でさらっと流してください。

先日より「vue.js入門 基礎から実践アプリケーション開発まで」という本を読みながらVue.jsの勉強を始めたのですが、HTMLもCSSもJavascriptも分かってない私はただただプログラムを延々と写経し続けるという修行をしておりました。

順調に進んでいたのですが、、、、

中規模・大規模向けのアプリケーション開発③でつまづいてしまいました。あまりにもわからなすぎるので試したことの備忘録としてここに残しておきたいと思います。現時点で3日ほど進捗なしです。進捗という言葉の意味を考えるいい機会となりました。

何につまづいているのか?

それはずばりテスト。実装自体は上手くできており想定した動きをしているのですがどうもテストが上手く通らない。十中八九テスト側のコードが悪いと睨んでいるのですが、写経プログラマーの私にはどこをどう直せばいいのか全くわかりません。テストコードが本通りなのはサンプルコードをダウンロードして置き換えてみたり比較してみたりしたのでプログラムを写すという点では問題無いと思っております。

まずはテストしたいコードとテストコードをご覧ください。
サポートページにソースファイルが公開されておりますので全体を把握した方は以下をご参照ください。

https://gihyo.jp/book/2018/978-4-297-10091-9/support
  • テストしたいコード
<script>
import KbnLoginForm from '@/components/molecules/KbnLoginForm.vue'

export default {
  name: 'KbnLoginView',
  components: {
    KbnLoginForm
  },

  methods: {
    handleLogin (authInfo) {
      return this.$store.dispatch('login', authInfo)
        .then(() => {
          this.$router.push({ path: '/' })
        })
        .catch(err => this.throwReject(err))
    },
    throwReject (err) { return Promise.reject(err) }
  }
}
</script>
  • テストコード(問題部分だけ抜粋)
import { mount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import KbnLoginView from '@/components/templates/KbnLoginView.vue'

const localVue = createLocalVue()
localVue.use(Vuex)

describe('KbnLoginView', () => {
  let actions
  let store
  let $router
  let LoginFormComponentStub
  const triggerLogin = (loginView, target) => {
    const loginForm = loginView.find(target)
    loginForm.vm.onlogin('foo@domain.com', '12345678')
  }

  beforeEach(() => {
    LoginFormComponentStub = {
      name: 'KbnLoginForm',
      props: ['onlogin'],
      render: h => h('p', ['login form'])
    }
    $router = {
      push: sinon.spy()
    }
    actions = {
      login: sinon.stub()
    }
    store = new Vuex.Store({
      state: {},
      actions
    })
  })

  describe('ログイン', () => {
    let loginView
    describe('成功', () => {
      beforeEach(() => {
        loginView = mount(KbnLoginView, {
          mocks: { $router },
          stubs: {
            'kbn-login-form': LoginFormComponentStub
          },
          store,
          localVue
        })
      })

      it('ボードページのルートにリダイレクトすること', done => {
        actions.login.resolves()

        triggerLogin(loginView, LoginFormComponentStub)

        loginView.vm.$nextTick(() => {
          expect($router.push.called).to.equal(true)
          expect($router.push.args[0][0].path).to.equal('/')
          done()
        })
      })
    })
  })
})

  • どんなエラーが発生するのか
ERROR LOG: '[vue-test-utils]: could not overwrite property $router, this is usually caused by a plugin that has added the property as a read-only value'
ERROR LOG: '[Vue warn]: Error in nextTick: "AssertionError: expected false to equal true"

ここまでただ写すことばかりに集中しておりコードの内容を全く理解していなかった私は焦りました。あれ?テストうまくいかない?なんでだろ。ソース間違ってるかな?間違ってない。調べてみよ。とGoogle先生に訪ねて回ることにしました。

まず現時点での私の理解

ここでは画面遷移で利用している「$routerの値が想定しているものであるか」をチェックしていると認識してます。

expect($router.push.called).to.equal(true)
expect($router.push.args[0][0].path).to.equal('/')
  • $router.pushが呼び出されているか
  • $router.push.args[0][0].pathの値が'/'になっているか

ログインフォームをスタブにすることでテストの範囲を狭めていることは理解できてます。ただエラーがでる。なぜかでる。サポートページを見ても対象箇所の訂正は無いしほんとにお手上げ;

調べたこと

ERROR LOG: '[vue-test-utils]: could not overwrite property $router, this is usually caused by a plugin that has added the property as a read-only value'

まずはこのエラーから。ぱっと見$routerはread-onlyだからそこんとこよろしく!的なことが書いてます。
なるほど。そういうことですか!

以下ページに回避方法的なことが記載されてます。

https://stackoverflow.com/questions/55523639/vue-test-utils-could-not-overwrite-property-route-this-is-usually-caused-by-a

どうもvue-test-utilsのバグだからこれをprocess.env.NODE_ENVに追加してみ?的なことが書かれてます。コメントを読むとそれは違うんじゃね?とかいう議論もされてますがひとまず置いておきましょう。

if (!process || process.env.NODE_ENV !== 'test') {
    Vue.use(VueRouter)
}

ほう、これを追加ね。りょうかいー。
・・・・・・・・・・・・・だからどこにだよ?わからん!!ソースコード検索かけても全然入れられそうなところ見つけられん。

でも何かの参考になるかもしれない。。。。。この記事を曲解して私は以下を試してみました。テストコードvue-router使ってないけど設定してみよう!

import { mount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import VuexRouter from 'vue-router'
import KbnLoginView from '@/components/templates/KbnLoginView.vue'

const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueRouter)

結果:何も変わらん

違うのね?分かってましたよ。では公式を参照してみましょう。

ここにそれっぽいことが書かれていますね。

https://vue-test-utils.vuejs.org/guides/using-with-vue-router.html

You should never install Vue Router on the Vue base constructor in tests. Installing Vue Router adds $route and $router as read-only properties on Vue prototype.

改善方法としては、、、、、いやこれもうやってみたよ。。。localVue.use(VueRouter)

しかしどう考えても$routerのモックが上手くうごいてないのは明白。誰かが同じようなコード書いてないかな?多分検索したらあるよね?そうに決まってるよね?

、、、、、、ないです。全然見つかりません。もしかしてこの書き方は一般的では無いのか?と思い始めてます。

これとか

$router = {
  push: sinon.spy()
}

これ

mocks: { $router }

さらにはこれ

$router.push.called
$router.push.args[0][0].path

わからねぇえええええ。次だ次。次のエラーから先に解決だ

ERROR LOG: '[Vue warn]: Error in nextTick: "AssertionError: expected false to equal true"

たぶん想定しているのはTrueだけどFalse返ってきてますよ。かな?コンソールログで確認してみました。
こんな感じ。

loginView.vm.$nextTick(() => {
  console.log($router.push.called)
  expect($router.push.called).to.equal(true)
  expect($router.push.args[0][0].path).to.equal('/')
  done()
})

すると、、、、そうですね分かってました。

LOG LOG: false

ついでにこちらもみておきましょう

console.log($router)

オブジェクトとしてはちゃんと登録されてるみたいです。ただ結局モックとしての役割を果たせてない模様。

LOG LOG: Object{push: spy}

なんでなの?おもしろいね。
私の想像ではグローバルの$routerとローカルの$routerの扱い部分に怪しさを感じています。ただ、どうすればいいか全く検討もつかないです。

もういっそのことRouterPush(path)みたいな関数をつくってその引数と呼び出しをチェックした方ができそうな気さえしてきました。。。。。ほんとにこの実装で上手くテスト通るのかなぁ。。。

他にも、おっ?これは参考になるかも!と思ったサイト

https://www.slideshare.net/Joe_noh/vuejs-82203019

ですがわからなかったです。shallowMountももしかすると関係するかもしれないとおもって変えてみたりしたけど改善せず。もう$routerでだめなら別の変数用意してやってみよvrouterみたいなの作ってみたりいろいろ試してみたけどダメだった;もちろん別の変数を用意した場合はread-onlyのエラーは消えましたが根本的に動いてないので意味なかったです。。。。

改善しなかったけど。。。

Vue.jsとテストについては理解度がだいぶ上がった気がします。実装されてるコード自体は正常に動いているので問題ないといえば問題ないのですがなんとなく気持ち悪いので先に進まず検証を繰り返しておりました。しかし、私の力では解決できそうもありません。残念ですが一旦テストは放棄して先に進みたいと思います。また、調べている最中にこの本はサンプルプログラムの完成までの説明は無いということを知りました。。。残念です。サンプルコードは公開されているので独力でなんとかするしか無いみたいです。そんな時、粘り強く検証した経験が活きると信じています。

あとだれか詳しい人教えてください。。。。

それから、よかったら私のブログと「Webデザインとコーディングのきほんのきほん」という本を読みながら作ったWebサイトご覧ください。

ブログ:https://www.ganbaru.life/
Webサイト:https://ganbaranai.ganbaru.life/
なんだかんだデザインの勉強をかねて1ヶ月くらいチマチマ作ってた気がする。。。。こんなサイトに!?と驚きの声が上がると思います。