Search Unity

  1. Calling all beginners! Join the FPS Beginners Mods Challenge until December 13.
    Dismiss Notice
  2. It's Cyber Week at the Asset Store!
    Dismiss Notice

To Singleton or not to Singleton. How can I unit test my code with all these Singletons?

Discussion in 'Scripting' started by Reshima, Jun 6, 2017.

  1. Reshima

    Reshima

    Joined:
    Dec 10, 2012
    Posts:
    24
    On Unity, it seems that most of the "example" code, tutorials, and documentation, encourage the use of Singletons. I don't want to debate if it's good or bad, I just want solutions to a few things, so I'm going straight to the point:

    1. How can I unit test my code with all these Singletons? Specifically, mocking Singletons such as NetworkManager (I really don't want to rely on network to test parts of my code when I run the build pipeline).

    2. Have you ever used Zenject? What's your experience with it? Does it hurt performance, readability? How long does it take for them to update the lib to latest/beta version in case if it ever breaks?

    I can understand why Unity uses Singletons (it's just easy and fast to make your game with them) and, although I try to be "puritan" to a few patterns (louse coupling and S***), I'm leaning towards using them as well. I'm just concerned about being able to unit test and being able to mock a few singletons. Overall, what's your experience so far? What have you used and what do you recommend?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    6,763
    This is one of the biggest flaws of singletons.

    They're counter to unit testing.

    As for dependency injection...

    I personally don't use Zenject when working on games in unity. For one I find dependency injection overkill for my projects. Dependency injection is mostly useful for iterative development with a decently sized team of developers. Working on ever evolving enterprise software heavily benefits from dependency injection... oh, you have a data parser that needs to do some special work not originally considered when designing your structure, so the interface of it would need to be modified. Causing you to either refactor, or having to make a weird abstract class and then a hanging dependent class for this specific setup.

    Where as if you had been using dependency injection from the get go. You just, well... inject the new dependent code, so that it works. No refactoring!

    And of course, it's highly testable... since you can just inject your test/mock objects. Again, no refactoring to support testing.

    ...

    Thing is game design is a bit counter to this design.

    First and foremost, most game development has designers/artists/etc who are NOT programmers and need to be able to work within the game and set things up. Drop components onto objects, wiring some properties together, and go. Dependency injection introduces an extremely complicated object structure that even the most competent developers have a hard time wrapping their head around.

    And it's done in a setting where the benefits aren't exactly seen. Most games aren't long term iterative projects. I mean sure you iterate on design... but not in the way that dependency injection really shines.

    Of course, some can argue the counter. Often people who live and die by dependency injection. And I'm not trying to knock the concept of it... I just don't get as much of a benefit from it in a game development setting.

    And furthermore, if you're only attempting to use it because it's allows for easier unit testing... well, that's bad reason to be using dependency injection. The fact it allows for smoother unit testing is an auxiliary feature of the concept, not its core feature.

    ...

    If you're going to move forward with such an idea... well, give all of unities static classes and singletons clearer object identity.

    For example, I do this with the Time class and Random class:
    ITimeSupplier:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/ITimeSupplier.cs
    And implementation:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/SPTime.cs

    IRandom:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/IRandom.cs
    and implementation:
    https://github.com/lordofduct/space...lob/master/SpacepuppyBase/Utils/RandomUtil.cs

    This gives object identity to what are otherwise static interfaces.

    In the case of singletons, which already have object identity. Abstract that identity. Like with NetworkManager, you can just have a MockNetworkManager and a MyGameNetworkManager, both implementing NetworkManager. But you never actually access it by the NetworkManager.singleton interface, and rather inject your NetworkManager depending on context.
     
    Dean-Kuai and Reshima like this.
  3. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    431
    Zenject is great, but buy-in is hard to acquire when you have "experienced" developers who can do the job "just fine" without it. I've stopped pushing dependency injection in unity until it matures.

    Instead, to counter the singleton craze, I've switched the team over to a global Service locator implementation. Services can be mocked far more easily than direct, static singleton access, and the code is only a little bit more verbose (not really). A bonus of using a service locator is that the users can use interfaces, instead of concrete classes. Another bonus is that lazy-loading is not allowed with my service locators because lazy loading is a tool of the devil.

    Code (csharp):
    1. // Instead of concrete, hard-to-mock access like this
    2. SomeSingleton.Instance.DoSomething(parameters);
    3.  
    4. // we can get access like so
    5. Services.Get<SomeSingleton>().DoSomething(parameters);
    6.  
    7. // or better, where SomeSingleton : ISomeInterface
    8. Services.Get<ISomeInterface>().DoSomething(parameters);
    9.  
    10. // we can swap out services for testing pretty easily, where TestingSingleton : ISomeInterface
    11. Services.RegisterService<ISomeInterface>(new TestingSingleton());
    12.  
     
    dnnkeeper, LMan, MNNoxMortem and 3 others like this.
  4. Reshima

    Reshima

    Joined:
    Dec 10, 2012
    Posts:
    24
    Wow, thanks a lot. Using a service locator is great, an option I didn't think about. Thanks!
     
  5. forcepusher

    forcepusher

    Joined:
    Jun 25, 2012
    Posts:
    197
    Thumbs up for that design of service locator - this is literally all you need.
    I've used Zenject in some projects and I'm not a big fan of it because reflection magic ruins code navigation. Zenject cripples the performance as well, I had to come in and gut the MonoKernel from using Update(), LateUpdate() and FixedUpdate() in every object that has injections.
     
    Last edited: May 29, 2019
    MNNoxMortem likes this.
  6. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    437
    We use Zenject in a small to medium sized/complex project and it is wonderful and very easy to work with. However, be careful to avoid the ProjectInstaller, as it is just another Singleton and it's installers are installed project wide - tests or not.

    That should not happen and I've not seen that happen yet, unless I am not understanding what you mean exactly.

    Any DI framework is overkill if your project is simple and everything it does for you, you can do by hand. A DI framework is valuable when doing it by hand is more tedious than using the framework:

    • Different contexts and bindings or complex binding rules
    • Bindings to the same type, but different targets depending on complex rules
    • Complex Resolve Getter patterns.
    Otherwise a simple ServiceLocator or Poor Man's DI do absolutely fine. That said: I do like DI frameworks and advice against using it to anyone who does not require one.

    Personally I prefer Poor Man's DI and constructor injection wherever possible over ServiceLocator patterns as the dependencies are better visible.
     
    Last edited: Jan 30, 2019
  7. RidgeWare

    RidgeWare

    Joined:
    Apr 12, 2018
    Posts:
    65
    If you're making a small-to-medium size project by yourself, and you know your code inside out, to be honest Singletons are fine. Just don't go too crazy with them.

    I have about 5 in my project.
     
  8. Cogniad

    Cogniad

    Joined:
    Feb 17, 2017
    Posts:
    14
    Are these singletons monobehaviours? Where do they get constructed?
    I'm very interested in the service locator pattern, and I'd love to see an example of it being implemented in Unity.