Zenject入門その1　疎結合とDI Container

# はじめに

最近、Zenjectについて導入を検討する人が増えてきました。今回はそのZenjectがそもそも何のためのライブラリなのかを解説します。

# Zenjectとは

「[Zenject Dependency Injection IOC](https://assetstore.unity.com/packages/tools/integration/zenject-dependency-injection-ioc-17758)」は**依存性の注入のためのフレームワーク**と言われています。

## よくある勘違い

Zenjectを導入すると、次のようなことができるようになると思っている人が多いですが、**それは間違いです**。

 * Zenjectを入れると疎結合になる！
 * Zenjectを入れるとテストが書きやすくなる！
 * 何かよくわからないけど入れるとプログラムが書きやすくなる！

繰り返しますが、**上記の認識は間違いです。**

## Zenjectの正しい説明

Zenjectは疎結合な設計やテストを書きやすくするためのライブラリではありません。
順序が逆で、**疎結合やテストのことを考えて設計したときに発生してしまう問題を解決するものがZenject**です。
つまり、**あらかじめ疎結合な設計やテストを考慮したプログラムになってないとZenjectは導入しても意味がない**ということになります。

そのため、Zenjectのメリットを説明するためにはどうしても「疎結合な設計」に対する理解が必要になります。
そういった点を考慮すると、Zenjectは初心者向けのライブラリではありません。ある程度プログラミングに慣れて、設計について意識し始めたときに導入を検討するくらいでちょうど良いくらいです。

# 「疎結合」と「依存性の注入」

さきほどから何度も「疎結合」というワードが登場しています。これについて詳しく説明しましょう。
まずは「疎結合」の反対の概念にあたる「密結合」から解説します。

## 密結合

密結合とは、名前のとおり「密な結合状態」を表します。
設計においては、「あるクラスが**特定の**クラスにべったり依存している」という状態を表します。

たとえばUnity開発において、よく発生する密結合な場面は「Input」周りでしょう。

### 密結合なInput

たとえば、次のようなコードは密結合な設計になります。

```cs
class Mover : MonoBehaviour
{
    void Update()
    {
        if (Input.GetButton("Jump"))
        {
            Jump();
        }

        var inputVector = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        var isDash = Input.GetButton("Dash");

        // 移動
        Move(inputVector, isDash);
    }

    /// <summary>
    /// ジャンプする
    /// </summary>
    void Jump()
    {
        /*
         * 省略 
         */
    }

    /// <summary>
    /// 移動する
    /// </summary>
    /// <param name="direction">移動方向</param>
    /// <param name="isDash">ダッシュするか</param>
    void Move(Vector3 direction, bool isDash)
    {
        /*
         * 省略 
         */
    }
}
```

何が密結合かと言うと、「`UnityEngine.Input`」を直接参照して利用しているという点です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/47146/68d482cb-bcf2-7afa-3b7f-d3899dac2163.png)


つまり、このスクリプトは`UnityEngine.Input`以外の要素から入力値を取得することができなくなっています。

### 「密結合なInput」では何がダメなのか？

これ以上の機能拡張やテストを書かないというのであればこれで問題ありません。
逆に言うと、機能拡張やテストを書きたくなったときに問題が出てきます。

たとえば、

 * UnityEngine.Inputではなく、[Rewird](https://assetstore.unity.com/packages/tools/utilities/rewired-21676)のInputに差し替えたい
 * テストするときに任意のタイミングでInputイベントを差し込みたい

といったときにこのままでは対応ができません。

## 疎結合

疎結合とは、密結合の逆で、「**特定の**クラスに依存しない状態」になっていることを指します。
つまり、具体的なクラスには紐付かず、それを抽象化したインタフェースを参照する設計を指します。

### 密結合から疎結合へ

では、さきほどのInputの例をもとに、密結合から疎結合へと作り直してみます。
やるべき作業としては、「`Mover`が直接`UnityEngine.Input`を触らない」ようにしてしまいます。
つまり、**インタフェースを介してMoverはInputの状態を取得する**という設計にします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/47146/18a75525-5f09-a66e-a55c-31d7cbf14f9d.png)

```cs:InputProvider
using UnityEngine;

namespace Player
{
    interface IInputProvider 
    {
        bool GetDash();
        bool GetJump();
        Vector3 GetMoveDirection();
    }
}
```

```cs:UnityEngine.Inputを使うInputProvider
namespace InputProviders
{
    public class UnityInputProvider : IInputProvider 
    {
        public bool GetDash()
        {
            return Input.GetButton("Dash");
        }

        public bool GetJump()
        {
            return Input.GetButton("Jump");
        }

        public Vector3 GetMoveDirection()
        {
            return new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        }
    }
}

```

```cs:疎結合化したMover
using UnityEngine;

namespace Player
{
    class Mover : MonoBehaviour
    {
        private IInputProvider inputProvider;

        public void SetInputProvider(IInputProvider input)
        {
            inputProvider = input;
        }

        void Update()
        {
            if (inputProvider.GetJump())
            {
                Jump();
            }

            var inputVector = inputProvider.GetMoveDirection();
            var isDash = inputProvider.GetDash();

            //移動
            Move(inputVector, isDash);

        }

        /// <summary>
        /// ジャンプする
        /// </summary>
        void Jump()
        {
            /*
             * 省略 
             */
        }

        /// <summary>
        /// 移動する
        /// </summary>
        /// <param name="direction">移動方向</param>
        /// <param name="isDash">ダッシュするか</param>
        void Move(Vector3 direction, bool isDash)
        {
            /*
             * 省略 
             */
        }
    }
}
```

こうすることで、**Moverはいまどの実装を使っているのかを意識することなく**入力状態を取得することができるようになりました。
これが**疎結合な設計**という状態です。

### 疎結合にすることのメリット

疎結合化することにより、**モジュールの差し替え**が簡単にできるようになります。
モジュールを差し替えることで、「最初は`UnityEngine.Input`で実装して、途中で`Rewird`に差し替える」「テスト用のモジュールに差し替えてテストを実行する」といったことができるようになります。

たとえば、さきほどのMoverを例にテストを書いてみます。

### Moverのテストを書く

##### ちゃんと移動するように作ったMover

さきほどまでの`Mover`は説明のために実装を省略していたので、ちゃんと移動するように実装を追加します。

```cs
using UnityEngine;

namespace Player
{
    public class Mover : MonoBehaviour
    {
        [SerializeField]
        private float jumpPower = 5f;

        [SerializeField]
        private float defaultMoveSpeed = 10f;

        private CharacterController characterController;

        private IInputProvider inputProvider;

        private Vector3 moveDirection;

        public void SetInputProvider(IInputProvider input)
        {
            inputProvider = input;
        }

        void Start()
        {
            characterController = GetComponent<CharacterController>();
        }

        void Update()
        {
            moveDirection = Vector3.zero;

            if (inputProvider == null) return;

            if (inputProvider.GetJump())
            {
                Jump();
            }

            var inputVector = inputProvider.GetMoveDirection();
            var isDash = inputProvider.GetDash();

            //移動
            Move(inputVector, isDash);

            //重力加速度
            moveDirection = new Vector3(
                moveDirection.x,
                moveDirection.y + Physics.gravity.y * Time.deltaTime + characterController.velocity.y,
                moveDirection.z);

            //移動
            characterController.Move(moveDirection * Time.deltaTime);
        }

        /// <summary>
        /// ジャンプする
        /// </summary>
        void Jump()
        {
            if (!characterController.isGrounded) return;
            moveDirection = new Vector3(moveDirection.x, moveDirection.y + jumpPower, moveDirection.z);
        }

        /// <summary>
        /// 移動する
        /// </summary>
        /// <param name="direction">移動方向</param>
        /// <param name="isDash">ダッシュするか</param>
        void Move(Vector3 direction, bool isDash)
        {
            var speed = isDash ? 3 : 1; //ダッシュすると3倍速い
            moveDirection += (direction * speed * defaultMoveSpeed);
        }
    }
}

```

そして、これに対応するテスト(PlayModeTest)がこちらです。

```cs:MoverTest.cs
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;
using InputProviders;
using Player;
using UnityEngine.SceneManagement;

public class MoverTest
{
    private GameObject targetGameObject;
    private TestInputProvider testInputProvider;

    [SetUp]
    public void Init()
    {
        testInputProvider = new TestInputProvider();
        SceneManager.LoadScene("TestScene");
    }

    [TearDown]
    public void Finish()
    {
        testInputProvider.Reset();
        targetGameObject.transform.position = Vector3.zero;
    }

    private void InitLazy()
    {
        if (targetGameObject == null)
        {
            targetGameObject = GameObject.Find("Player");
            var mover = targetGameObject.GetComponent<Mover>();
            mover.SetInputProvider(testInputProvider);

            testInputProvider.Reset();
            targetGameObject.transform.position = Vector3.zero;
        }
    }

    [UnityTest]
    public IEnumerator 移動できる()
    {
        InitLazy();

        // 前に進む
        testInputProvider.MoveDirection = Vector3.forward;

        yield return new WaitForSeconds(1);

        // 前進している
        Assert.True(targetGameObject.transform.position.z > 0);
    }


    [UnityTest]
    public IEnumerator ダッシュできる()
    {
        InitLazy();

        // 前に進む
        testInputProvider.MoveDirection = Vector3.forward;

        yield return new WaitForSeconds(1);

        // 通常移動で進んだ距離
        var normal = targetGameObject.transform.position.z;

        testInputProvider.Reset();
        targetGameObject.transform.position = Vector3.zero;
        yield return null;

        // ダッシュする
        testInputProvider.IsDash = true;
        testInputProvider.MoveDirection = Vector3.forward;

        yield return new WaitForSeconds(1);

        // ダッシュした方が遠くに移動できている
        Assert.True(targetGameObject.transform.position.z > normal);
    }

    [UnityTest]
    public IEnumerator ジャンプできる()
    {
        InitLazy();

        // ジャンプする
        testInputProvider.IsJump = true;

        yield return new WaitForSeconds(1);

        // ジャンプできている
        Assert.True(targetGameObject.transform.position.y > 0);
    }
}
```

重要なのは`InitLazy()`内のこのあたりです。

```cs
targetGameObject = GameObject.Find("Player");
var mover = targetGameObject.GetComponent<Mover>();
mover.SetInputProvider(testInputProvider);
```

`SetInputProvider()`を使って、テスト用の`InputProvider`を`Mover`に登録しています。
このように、通常時は`UnityInputProvider`を渡し、テスト時は`TestInputProvider`に差し替える、みたいなことが疎結合化していると簡単に行えるようになります。



# 疎結合化することの問題点

疎結合化することによるデメリットはいくつかあるのですが、もっとも大きな問題は「**利用するインスタンスの決定をどうやって行うのか？**」というものです。

さきほどの`Mover`の説明ではしれっと「`SetInputProvider()`に`UnityInputProvider`を与えればよい」みたいな説明をしましたが、**じゃあ誰がどのタイミングでSetInputProvider()を呼び出すのか？**という問題が起きることを無視していました。

この問題を回避する方法として、手法として「ServiceLocatorパターン」と「Dependency Injectionパターン」というものがあります。

## ServiceLocator パターン

ServiceLocatorパターンは、簡単に言えば「ServiceLocatorというオブジェクトに対して依存関係を解決しにいく」という手法です。
たとえば次のような実装はServiceLocatorパターンになります。

```cs:依存関係の登録と取得ができるServiceLocator
public class ServiceLocator
{
    private Dictionary<Type, object> container = new Dictionary<Type, object>();

    public T Resolve<T>()
    {
        return (T)container[typeof(T)];
    }

    public void Register<T>(T instance)
    {
        container.Add(typeof(T), instance);
    }
}
```

```cs:ServiceLocatorを取得できるシングルトン
public class ServiceLocatorProvider : SingletonMonoBehaviour<ServiceLocatorProvider>
{
    public ServiceLocator Current { get; private set; }

    private void Awake()
    {
        Current = new ServiceLocator();
        // 依存関係を登録
        Current.Register<IInputProvider>(new UnityInputProvider());
    }
}
```

```cs:Mover(ServiceLocatorを使って依存性を解決する)

namespace Player
{
    public class Mover : MonoBehaviour
    {
        //関係ない部分は省略


        private IInputProvider inputProvider;

        void Start()
        {
            // サービスロケータから利用するインスタンスを取得する
            inputProvider = ServiceLocatorProvider.Instance.Current.Resolve<IInputProvider>();
        }

        void Update()
        {
           // 以下略
}
```

このように、依存関係の解決をServiceLocatorというオブジェクトに集約し、static変数経由でアクセスして解決する手法をServiceLocatorパターンと呼びます。

この手法は実装がシンプルで使い方も単純というメリットがあるのですが、一方で開発規模が大きくなって依存関係が複雑化すると途端にリファクタリングが困難になるというデメリットも持っています。（ServiceLocatorへの密結合がリファクタリング時に邪魔になってしまう。）

そのため、「規模が小さいプロジェクトで依存関係を解決する」といった場面や、「アプリケーションのグローバル設定を参照するのに使う」といったような限定的な場面で使うなど、できるだけ狭い範囲でのみServiceLocatorパターンは利用することを推奨します。

そして、規模が大きくなってきた場合は「Dependency Injectionパターン」を利用すると良いでしょう。

## Dependency InjectionとDI Container

あらためて、「**依存性の注入(Dependency Injection)**」について解説します。
Dependency Injectionパターンでは`DI Container`と呼ばれるオブジェクトが登場します。

「依存性の注入」は日本語訳がわかりにくいのですが、意味としては「依存オブジェクトの注入」が近いです。
つまり、「状況に応じて適切なオブジェクトをインスタンスに設定していくパターン」みたいな意味です。

（依存性の注入、は長いので以降はDIと呼称します。）

### DI Container

DIを実行するにあたり、`DI Container`という概念が登場します。
`DI Container`は簡単に言えば「**環境にあらかじめ依存関係を設定しておくと、それに応じて自動的にインスタンスを設定してまわってくれる便利な存在**」です。

ServiceLocatorパターンではオブジェトが自ら必要なインスタンスを取りに行くという仕組みでしたが、`DI`ではその逆を行います。
環境に存在する`DI Container`が、状況に応じて自動的にオブジェクトに対してインスタンスを設定してくれます。（仕組みはともかくとして、そういう便利な存在です。）

そして、この`DI Container`の機能を提供してくれるライブラリが**Zenject**になります。


# ZenjectのDI Containerを使ってみる

それでは、実際にZenjectの`DI Container`を設定し、実際に使ってみましょう。
さきほどの`Mover`の例をもとに行っています。

## 1. Zenjectをインストールする

[Asset Store](https://assetstore.unity.com/packages/tools/integration/zenject-dependency-injection-ioc-17758)から導入すれば終わりです。

## 2. Installerを記述する

`Installer`とは、Zenjectの`DI Container`に対して依存関係を記述してあげる場所になります。

いくつか種類があるのですが、今回は`MonoBehaviour`として扱うことができる`MonoInstaller`を利用します。

ProjectView -> Create -> Zenject -> Mono Installer から作成ができます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/47146/5d1895eb-0dd7-48aa-9a7a-25f0d1f9ee7a.png)

スクリプトが生成されたら、そこに次のような内容を記述します。

```cs:UnityInputProviderを使うInstaller
using InputProviders;
using Player;
using Zenject;

namespace ZenjectSample
{
    public class UnityInputInstaller : MonoInstaller<UnityInputInstaller>
    {
        public override void InstallBindings()
        {
            Container
                .Bind<IInputProvider>()   // IInputProviderが要求されたら
                .To<UnityInputProvider>() // UnityInputProviderを生成して注入する
                .AsCached();              // ただし、UnityInputProviderが生成済みなら使い回す
        }
    }
}
```

`DI Container`に対して、どのインタフェースが要求されたら、どのインスタンスを注入するか、そのときにインスタンスをどう扱うか、設定することができます。
今回は「`IInputProvider`が要求されたら、`UnityInputProvider`を注入する、そのときに`UnityInputProvider`がキャッシュされているならそれを使う」という設定をしています。


## 3. Contextを用意する

`Context`は`Installer`の影響範囲を設定するものです。主に次の2種類があります。

 * Project Context : そのUnityProject全体に影響を及ぼす
 * Scene Context : そのContextが配置されたシーンにのみ影響を及ぼす

今回は`Scene Context`を使います。
Installerを配置したいシーンのHierarchyView上で、

右クリック -> Zenject -> Scene Context でSceneContextが配置されます

![image.png](https://qiita-image-store.s3.amazonaws.com/0/47146/ce934811-614d-1f0b-78f2-836aef5b83d7.png)

## 4. ContextにInstallerを設定する

生成した`Scene Context`に、さきほどの`UnityInputInstaller`を登録します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/47146/b3a3d036-d7d7-c533-44c9-085f627f2bd6.png)

場所はどこでもいいので、`UnityInputInstaller`をどこかのGameObjectに貼り付けます（今回はそのまま`Scene Context`のGameObjectに配置)。
そして、`Scene Context`上の`Installers`欄にこれを登録します。

**これでこのシーン上で`DI Continer`が有効になりました。**

## 5. DI ContainerからのInjectを受け入れられるようにする

`Mover`スクリプトを修正し、`DI`を受けれいられるようにします。
記法はいくつかありますが、`MonoBehavior`を継承したオブジェクトの場合は「Field Injection」「Property Injection」「Method Injection」のどれかしか利用できません。

今回はどの記法で書いても挙動は同じになります。


### Field InjectionでDI

フィールド変数に`[Inject]`属性をつけることで、そこに`DIContainer`がオブジェクトを注入してくれます。

```cs
using UnityEngine;
using Zenject;

namespace Player
{
    public class Mover : MonoBehaviour
    {
        [Inject]
        private IInputProvider inputProvider;

        // 以下略
```

### Property InjectionでDI

プロパティに`[Inject]`属性をつけることで、そこに`DIContainer`がオブジェクトを注入してくれます。

```cs
using UnityEngine;
using Zenject;

namespace Player
{
    public class Mover : MonoBehaviour
    {
        [Inject]
        private IInputProvider inputProvider { get; set; }

        // 以下略
```

### Method InjectionでDI

メソッドに`[Inject]`属性をつけることで、その引数に応じて`DIContainer`がオブジェクトを注入してくれます。

```cs
using UnityEngine;
using Zenject;

namespace Player
{
    public class Mover : MonoBehaviour
    {
        private IInputProvider inputProvider;

        [Inject]
        private void Injection(IInputProvider inputProvider)
        {
            this.inputProvider = inputProvider;
        }

        // 以下略
```

## 6. 実行する

あとはこのままゲームを実行すると、**シーンがロードされたタイミングで`DI Container`がオブジェクトを探してまわり、注入を自動的に実行してくれます。**

今回の場合は、シーンをロードすると`Mover`に`UnityInputProvider`が自動的に注入されることになります。

## 7. GameObject.Instantiate するときは

`GameObject.Instantiate`を直接実行した場合、そのオブジェクトは`DI Container`が認識してくれないためDIが実行されません。
DIを実行しながらGameObjectを生成する場合は次の方法を取る必要があります。

### Factoryを使う場合

指定したPrefabから`GameObject`を生成する`Factory`を用意し、その`Factory`経由でオブジェクトを生成することでDIを実行することができます。

* [Creating Objects Dynamically Using Factories](https://github.com/modesttree/Zenject/blob/master/Documentation/Factories.md)

```cs:MoverFactory
class MoverFactory : PlaceholderFactory<Mover>
{
    // 中身は空で良い
}
```

```cs:Installer
public class UnityInputInstaller : MonoInstaller<UnityInputInstaller>
{
    [SerializeField] private GameObject MoverPrefab;

    public override void InstallBindings()
    {
        Container
            .Bind<IInputProvider>()   // IInputProviderが要求されたら
            .To<UnityInputProvider>() // UnityInputProviderを生成して注入する
            .AsCached();              // ただし、UnityInputProviderが生成済みなら使い回す


        Container
            .BindFactory<Mover, MoverFactory>() //MoverのFactoryはMoverFactoryであると紐付ける
            .FromComponentInNewPrefab(MoverPrefab); //生成はPrefabをベースに行う
    }
}
```

```cs:使い方
public class MoverGenerator: MonoBehaviour
{
    [Inject] private MoverFactory factory;

    void Start()
    {
        // 3つ作ってみる
        factory.Create();
        factory.Create();
        factory.Create();
    }
}
```

### DI Containerを直接使う場合

別の方法として、`DI Container`を直接DIして使うこともできます。ただしこちらのパターンは`Service Locator`パターンとやってることは同じになります。


```cs:Containerを直接使う場合
public class MoverGenerator: MonoBehaviour
{
    [SerializeField] private GameObject MoverPrefab;

    [Inject] private DiContainer container;

    void Start()
    {
        // Containerから直接生成する
        container.InstantiatePrefab(MoverPrefab);
    }
}
```

なお、`DI Container`を直接操作することで、`AddComponent`相当の処理を実行することもできます。

```cs:AddComponentをContainer経由で行う
public class MoverGenerator: MonoBehaviour
{
    [Inject] private DiContainer container;

    [SerializeField] private GameObject EmptySpherePrefab;

    void Start()
    {
        // 何もついてないSphereをPrefabから作る
        var go = Instantiate(EmptySpherePrefab);

        // DIが不要なコンポーネントはそのままAddComponent
        go.AddComponent<CharacterController>();

        // DIが必要なコンポーネントはContainer経由で行う
        container.InstantiateComponent<Mover>(go);
    }
}

```


## 8. Container経由でInstantiateできない場合では

`PhotonNetwork.Instantiate`を使っているなど、`Container`経由で`Instantiate`できないシチュエーションなどがあると思います。
その場合は**ZenAutoInjector**コンポーネントをPrefabにあらかじめ貼り付けて置くことで、生成時に後追いでそのオブジェクトに対してDIが実行されるようになります。

ただし、**この場合は`Start()`よりあとのタイミングでDIが実行される**ことになります。そのためDIが確実に完了していることを検知した上で`Update()`を実行するなどの工夫が必要になってきます。

たとえばMethodInjectionを使ってDIのタイミングでフラグを立てる、などするとよいでしょう。


```cs
using UnityEngine;
using Zenject;

namespace Player
{
    public class Mover : MonoBehaviour
    {
        private bool isInitialized;

        private IInputProvider inputProvider;

        [Inject]
        private void Injection(IInputProvider inputProvider)
        {
            this.inputProvider = inputProvider;

            // DIが実行されたらフラグを立てる
            isInitialized = true;
        }

    void Update()
    {
        if(!isInitialized) return; //初期化が終わってないならUpdateを実行しない
```

## 9. ZenjectBinding

ZenjectBindingは、`DI Container`の機能を応用したもので、シーンロード時に`MonoBehaviour`の参照解決を行ってくれる機能です。

### 例

次のような`MonoBehaviour`があったときに、参照をZenjectBindを使って解決してみます。

```cs
using UnityEngine;

/// <summary>
/// 何かのマネージャ
/// </summary>
public class HogeManager : MonoBehaviour
{

}
```

```cs
using UnityEngine;
using Zenject;

public class Fuga : MonoBehaviour
{
    // HogeManagerがほしい
    [Inject] private HogeManager hogeManager;
}
```

続いて、`Zenject Binding`というコンポーネントをどこでもいいので配置します。
ここの`Component`欄に、DIしたいコンポーネントを登録します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/47146/e9a5e1b5-5c36-4fba-b856-d5bd21bf2bce.png)


以上です。これで自動的に`HogeManager`が要求されている場所に注入されるようになりました。

### Bind Type

![image.png](https://qiita-image-store.s3.amazonaws.com/0/47146/19e97dcd-e17b-acdd-be06-a8d8f981a17f.png)

ついでに覚えておくとよいのが、この`Bind Type`です。それぞれ次のような意味です。

* **Self** : 指定コンポーネントと型が完全に一致した場合のみDIする
* **AllInterfaces** : 指定コンポーネントが実装するインタフェースが要求されている場合のみDIする
* **AllInterfacesAndSelf** : Self + AllInterfacesになる
* **BaseType** : 指定コンポーネントのインタフェースではなく、基底クラスが要求されている場合のみDIする

### 仕組み

ZenjectBindingは、次のような`MonoInstaller`を自動生成しContextに登録してくれる機能にすぎません。

```cs:ZenjectBindingが生成するInstallerと同等の内容
public class GameInstaller : MonoInstaller
{
    public HogeManager hogeManager;

    public override void InstallBindings()
    {
        Container.Bind<HogeManager>().FromInstance(hogeManager);
    }
}
```

# Zenjectを使ってテストを実行する

`ZenjectUnitTestFixture` / `ZenjectIntegrationTestFixture`を使うと、テスト実行時に`Container`を設定することができるようになります。

なお、 `ZenjectIntegrationTestFixture` はAssetStoreからZenjectを導入した場合はデフォルトでは使えないようになっています。
`Assets/Plugins/Zenject/OptionalExtras/ZenjectIntegrationTestFixture.zip` を解凍して中のファイルを手動で配置することで利用可能になります。

## MoverTestのZenject版

`PreInstall()`と`PostInstall()`の間に`DI Container`に対して操作することができるようになっています。
このタイミングで`DI Container`にテスト用のオブジェクトを注入するように設定してあげることで、テストモジュールが差し込まれてテストができるようになります。

```cs
using Zenject;
using System.Collections;
using InputProviders;
using NUnit.Framework;
using Player;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;

public class MoverTestZenject : ZenjectIntegrationTestFixture
{
    private TestInputProvider testInputProvider;
    private GameObject targetGameObject;

    [SetUp]
    public void Init()
    {
        SceneManager.LoadScene("TestScene");
        testInputProvider = new TestInputProvider();
    }

    void CommonInstall()
    {
        PreInstall();

        Container.Bind<IInputProvider>().FromInstance(testInputProvider);

        PostInstall();
    }


    [TearDown]
    public void Finish()
    {
        testInputProvider.Reset();
        targetGameObject.transform.position = Vector3.zero;
    }

    private void InitLazy()
    {
        if (targetGameObject == null)
        {
            targetGameObject = GameObject.Find("Player");

            testInputProvider.Reset();
            targetGameObject.transform.position = Vector3.zero;
        }
    }

    [UnityTest]
    public IEnumerator MoveTest()
    {
        CommonInstall();

        InitLazy();

        // 前に進む
        testInputProvider.MoveDirection = Vector3.forward;

        yield return new WaitForSeconds(1);

        // 前進している
        Assert.True(targetGameObject.transform.position.z > 0);
    }


    [UnityTest]
    public IEnumerator DashTest()
    {
        CommonInstall();

        InitLazy();

        // 前に進む
        testInputProvider.MoveDirection = Vector3.forward;

        yield return new WaitForSeconds(1);

        // 通常移動で進んだ距離
        var normal = targetGameObject.transform.position.z;

        testInputProvider.Reset();
        targetGameObject.transform.position = Vector3.zero;
        yield return null;

        // ダッシュする
        testInputProvider.IsDash = true;
        testInputProvider.MoveDirection = Vector3.forward;

        yield return new WaitForSeconds(1);

        // ダッシュした方が遠くに移動できている
        Assert.True(targetGameObject.transform.position.z > normal);
    }

    [UnityTest]
    public IEnumerator JumpTest()
    {
        CommonInstall();

        InitLazy();

        // ジャンプする
        testInputProvider.IsJump = true;

        yield return new WaitForSeconds(1);

        // ジャンプできている
        Assert.True(targetGameObject.transform.position.y > 0);
    }
}
```

# まとめ

* Zenjectを使うためには、あらかじめ疎結合な設計にしておく必要がある
* Zenjectは依存性の注入を行うためのフレームワークである
* DI Containerの扱いが非常に重要になる
* 設計の勉強がしたいなら[この資料](https://www.slideshare.net/torisoup/unityzenject)をみましょう