【Javascript】thisを参照するクラスメソッドのテスト

#概要
Javscriptでクラスのメソッドをテストしようとしたときにthisの参照で失敗しました。
`call`を使うことでthisを参照するクラスのメソッドもテストすることができたので、やりかたをまとめました。
#問題
以下の通り、`test`関数と被テスト用の`Tested`クラスを定義します。

```Javascript
//テスト用関数
function test(func, arg, expected){
  const result = func(arg);
  if(result === expected){
    console.log("OK");
  }
  else{
    console.log("FAIL");
  }
}

//被テストクラス
class Tested {
  constructor(){
    this.x = 3;
  }
  getX(){
    return this.x;
  }
}
```

`test`はテストされる関数（`func`）、テストされる関数に代入する引数（`arg`）、期待する値（`expected`）を引数として、`func`に`arg`を代入した結果とexpectedを比較した結果をコンソールに出力します。（なお、このテスト関数は2つ以上引数を取る関数に対応していなかったり、objectを出力する関数のチェックがうまくできなかったりして汎用性が低いのですが、本質ではないので、今回は気にしないことにします）

`Tested`クラスは`this.x`の値を返す`getX`メソッドをもちます。`this.x`にはコンストラクタで3が代入されているので、getXは原則3を返します。

```Javascript
let tested = new Tested();
console.log(tested.getX() === 3);
//true
```

一方で、`tested.getX`の戻り値が3であるかを`test`関数で調べると、OKが出力されることが期待されますが、以下のエラーが返ってきます。

```Javascript
test(tested.getX, null, 3);
//Uncaught TypeError: Cannot read property 'x' of undefined
//    at getX (<anonymous>:16:17)
//    at test (<anonymous>:2:18)
//    at <anonymous>:22:1
```

#原因
`getX`の`return this.x`の行で、「undefinedのxを読めない」と言われているので、`this`が`undefined`となっていることが原因だとわかります。

`this`の仕様が難しくてすべてを理解できてはいないのですが、どうも`test`の引数として`tested.getX`を入れてしまったので、`getX`が参照する`this`が`tested`インスタンスではなく、`test`関数になってしまい、`x`の参照がうまく機能しないようです。

追記：`undefined`になる理由がよく割らなかったのですが、関数が参照する`this`はStrictモードでは`undefined`になるという仕様があるらしく、どうもそれっぽいです。
（参考：[JavaScriptのthisの挙動を完全に理解した話](https://qiita.com/renesisu727/items/c77954a437afa1d9e711) ）

#解決方法
`Function.prototype.call()`を使うことで、クラスメソッドのテストを正しく行うことができました。

```Javascript
//tested.getXをfunction(_arg){return tested.getX.call(tested, _arg)}に置き換える
test(function(_arg){retrun tested.getX.call(tested, _arg)}, null, 3)
// OK
```

`Function.prototype.call`は関数が`this`として扱うべき変数を第一引数に指定することができます。
今回は`tested`を`this`として扱ってほしいので、`tested.getX.call(tested, _arg)`とすればよいです。
ただし`tested.getX.call`をそのまま`test`関数の引数に持ってくることはできないので、`_arg`だけを引数としてとる無名関数でラップします。

参考：
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/call

#おまけ
`test`関数が複数引数を扱えるように拡張しておくことで、`call`を無名関数でラップしなくてもよくなりコード量を削減できるかも、、、と思ったのですが、callを引数に指定するとどうもうまく機能しないようです。

```Javascript
//引数はfunc, arg1,arg2, ..., expectedのように指定する
function test(){
  //複数引数に対応できるように、argumentsで引数を取得する
  const _arguments = [].slice.call(arguments);
  const func = _arguments[0];//最初の引数
  const args = _arguments.slice(0,-1);//最初と最後以外の引数
  const expected = _arguments[_arguments.length-1]//最後の引数
  
  const result = func(...args); //...で引数のリストを展開する
  if(result === expected){
    console.log("OK");
  }
  else{
    console.log("FAIL");
  } 
}

//さっきまでと一緒
class Tested {
  constructor(){
    this.x = 3;
  }
  
  getX(){
    return this.x;
  }
}

let tested = new Tested();
//callを使う分、引数がひとつ増えた扱いにして、テストする
test(tested.getX.call, tested, null, 3);
//VM352:9 Uncaught TypeError: func is not a function
//    at test (<anonymous>:9:18)
//    at <anonymous>:1:1
```
`call`を別変数に代入して関数のように扱うことはできないようです。`call`は`prototype`のメソッド（？)として定義されますが、別変数に代入した時点で`prototype`の参照先が変わってしまうためかもしれません（よくわかっていません）

#おまけ２
thisクラス側を書き換えることが可能な状況であれば、アロー関数を使ってメソッドを定義すれば、thisに悩まされなくて済みそうです。
今まであまり良くわかっていなかったのですが、functionとアローの違いはこういうところにあるんですね。

```Javascript
class Tested {
  constructor(){
    this.x = 3;
  }
	
  getX = () => {
    return this.x;
  }
}

let tested = new Tested();
console.log(test(tested.getX,3));
//true
```