Injecter.Unity
Since version 3.0.1 you need to provide the following dlls yourself:
- Injecter
- Microsoft.Extensions.DependencyInjection
- Microsoft.Extensions.DependencyInjection.Abstractions
Note
The recommended way of installing NuGet packages is through the UnityNuget project
Fundamentals
The Injecter.Unity
lets you set up the following flow:
- A "composition root" is initialized part of the entry point of the application
- Create a script which needs to be injected
- Choose an injection method:
- Either use the helper components
- Add
MonoInjector
to theGameObject
hosting the script MonoInjector
runs atAwake
, and it's execution order (int.MinValue
- first) is run before your own component'sAwake
. Every injected script will have it's ownIServiceScope
derived from the root scope. This scope can be retrieved through theIScopeStore
, and the owner of the scope is the script being injected- When the
GameObject
is destroyed,MonoDisposer
will run during theOnDestroy
method, with an execution order ofint.MaxValue
- last
- Add
- Or derive from
MonoBehaviourInjecter
- The base class
Awake
method will do the injections, and theOnDestroy
method will dispose of the scope
- The base class
- Either use the helper components
Getting started
Install dependencies
- Install
Injecter
andMicrosoft.Extensions.DependencyInjection
through UnityNuget. - Install
Injecter.Unity
through openupm
openupm add com.injecter.unity
Setup root
Either create manually, or through the Assets / Injecter
editor menu, create your composition root.
#nullable enable
using Injecter;
using Microsoft.Extensions.DependencyInjection;
using UnityEngine;
public static class AppInstaller
{
/// <summary>
/// Set this from test assembly to disable
/// </summary>
public static bool Run { get; set; } = true;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)]
public static void Install()
{
if (!Run) return;
var serviceProvider = new ServiceCollection()
.Configure()
.BuildServiceProvider(true);
// Injected scripts will get the root service provider from this instance
CompositionRoot.ServiceProvider = serviceProvider;
Application.quitting += OnQuitting;
/// <summary>
/// Will dispose of all services when quitting
/// </summary>
async void OnQuitting()
{
Application.quitting -= OnQuitting;
await serviceProvider.DisposeAsync().ConfigureAwait(false);
}
}
public static IServiceCollection Configure(this IServiceCollection services)
{
services.AddInjecter(o => o.UseCaching = true);
// TODO: Add services
return services;
}
}
Inject using the helper components
Inject into MonoBehaviours
Create a script which will receive injection
[RequireComponent(typeof(MonoInjector))]
public class MyScript : MonoBehaviour
{
[Inject] private readonly IMyService _service = default!;
}
Add MonoInjector
If you decorate your script with [RequireComponent(typeof(MonoInjector))]
then, when adding the script to a GameObject
the editor will add the MonoInjector
and the MonoDisposer
script to your GameObject
. If for some reason this does not happen (for example, when changing an already living script into one needing injection), either add the MonoInjector
component manually, or use the editor tools included to add the missing components to scenes or prefabs (will search all instances) through the editor menu Tools / Injecter / ...
Inject by inheriting from MonoBehaviourInjected
Create a script which will receive injection
public class MyScript : MonoBehaviourInjected
{
[Inject] private readonly IMyService _service = default!;
protected override void Awake()
{
base.Awake();
// Custom logic goes here
}
protected override void OnDestroy()
{
// Custom cleanup logic goes herer
base.OnDestroy();
}
}
Manual injection
When dynamically adding an injectable script to a GameObject
, you should not annotate the injected script with the RequireComponent
attribute, and add the MonoInjector
manually, like so:
var myObject = new GameObject("MyObject");
myObject.AddComponent<TestComponent>();
myObject.AddComponent<MonoInjector>();
public class MyComponent : MonoBehaviour
{
[Inject] private readonly MyService _service = default!;
}
Testing
You can load tests and prefabs with controlled dependencies when running tests inside Unity. To do this create the following class in your test assembly:
public static class InstallerStopper
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
public static void DisableAppInstaller()
{
if (Environment.GetCommandLineArgs().Contains("-runTests")
|| EditorWindow.HasOpenInstances<TestRunnerWindow>())
{
AppInstaller.Run = false;
}
}
}
This will stop your AppInstaller
from running when you execute tests. This will happen if either you have the test runner window open, or you are running Unity headless with the -runTests
parameter (typically during CI).
Warning
If you do this, then you must close the test runner window when entering play mode, otherwise the AppInstaller
will not run
In your tests set up the composition root manually
[UnityTest]
public IEnumerator My_Test_Does_Stuff()
{
CompositionRoot.ServiceProvider = new ServiceCollection()
.AddTransient<IService, MyTestService>()
.BuildServiceProvider();
// Do your tests, load scenes, prefabs, asserts etc...
CompositionRoot.ServiceProvider.Dispose();
(CompositionRoot.ServiceProvider as IDisposable)?.Dispose();
};
Note
You can also do the same in the test's Setup
or Teardown
stage
Migrating from 8.0.1
to 9.0.0
and above
If you want to continue using the inheritance setup, then change all base classes to be MonoBehaviourInjected
. The new base class will always create scopes.
If you want to migrate to the component-based injection:
- Set up a composition root as described above.
- Remove inheriting from the old
MonoBehaviourInjected
and similar classes - Optional - Decorate your injected scripts with
[RequireComponent(typeof(MonoInjector))]
- In the editor press the
Tools / Injecter / Ensure injection scripts on everyting
button - If a
GameObject
is missing theMonoInjector
orMonoDisposer
scripts, add them