Search Unity

Unit Testing ScriptableObjects

Discussion in 'Scripting' started by SeanMcTex, Oct 23, 2017.

  1. SeanMcTex

    SeanMcTex

    Joined:
    Dec 12, 2016
    Posts:
    7
    Howdy folks,

    I watched the talk on ScriptableObjects at Unite Austin 2017, and was very excited about the improvements to architecture they allow. But I'm working on implementing some of those learnings now, and am hard up against the fact that I can't create instances of ScriptableObjects in my unit tests as I normally would. When I try, Unity gives me the following when I run the test:

    OneTimeSetUp: UnityEngine.UnityException : ScriptableObject.ctor can only be called from the main thread.
    Constructors and field initializers will be executed from the loading thread when loading a scene.
    Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.

    Has anyone figured out an effective way to unit test their ScriptableObjects?

    Thanks!
    Sean
     
    wickedwaring likes this.
  2. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    I've never done it. Also I'm not sure what you're using SOs for, but if there's more than just data in it, I'm sure one of the following approaches will do it:

    1) Test it in your Unity application using the Assert API that is shipped with Unity, trying to cover most cases.
    2) Fake a SO base class that you inherit from during tests in VS (only really useful if you do not depend on other script types, as complexity would grow and source code would be cluttered with too many stubs and symbols)

    3) Probably test best:
    Seperate the logical parts / operations from the actual data and put it in a non-unity-specific class. This allows to test that seperately from your SO.
    The SO would mainly contain (optionally: exposed) data and reference fields.
     
    brandongui123alt likes this.
  3. SeanMcTex

    SeanMcTex

    Joined:
    Dec 12, 2016
    Posts:
    7
    Thanks for your suggestions, Suddoha! I was already using the Test Runner in Unity for this, which is where I was seeing the error. The thing I eventually hit on and got working well was to create a test instance of the ScriptableObject class (a CMS in this case) in my project using the Create menu, and then reference the instance that Unity created there through the AssetDatabase API:

    sut = (CMS)AssetDatabase.LoadAssetAtPath( pathToTestCMS, typeof(CMS) );

    This is working well, and allows me to have both the test instance, which points to test data, and a production instance that points to real stuff, in the project hierarchy. It's feeling pretty good now.
     
    Suddoha likes this.
  4. Mathijs_Bakker

    Mathijs_Bakker

    Joined:
    Apr 28, 2014
    Posts:
    25
    It's an old thread I know. But when someone search for this, this could be of any help.
    You can just instantiate an instance of the scriptable object like this:
    Code (CSharp):
    1. var sut = ScriptableObject.CreateInstance<MyScriptableObject>();
     
    JoeBeer, Xandir88 and SeanAtHandsome like this.
  5. tblank555

    tblank555

    Joined:
    Nov 3, 2016
    Posts:
    2
    The OP was seeing failures from trying to call a constructor from a thread other than the main thread. You will see the same failures even if you instantiate a ScriptableObject the proper way, by calling CreateInstance().

    The most appropriate way to run unit tests that depend on a ScriptableObject is what Suddoha suggested above, where you make your ScriptableObject subclass conform to an interface that you define, and you make your other classes use that interface instead of the ScriptableObject subclass. That way you can create a "mock" version of the ScriptableObject class that conforms to the same interface but does not inherit from ScriptableObject, and you can use that in your unit tests instead.
     
  6. Martin_Gonzalez

    Martin_Gonzalez

    Joined:
    Mar 25, 2012
    Posts:
    361
    Old thread but just wanted to add more info of why this error occurs

    Code (CSharp):
    1. OneTimeSetUp: UnityEngine.UnityException : ScriptableObject.ctor can only be called from the main thread.
    2. Constructors and field initializers will be executed from the loading thread when loading a scene.
    3. Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
    1) Just like above is said you cannot create new instances of ScriptableObjects (neither MonoBehaviours) with the new instruction. You need to use ScriptableObject.CreateInstance<MyScriptableObject>();

    2) You cannot use any UnityEngine classes in other threads, so if you have a code that go inside another tread you will need to create a separation between Unity infrastructure and your code domain. Like the comment above, creating an interface will allow you to do Dependency Injection and inject to your classes constructor an implementation that can be or a ScriptableObject or another pure C# object and will be also easier to test.
     
    jucasal likes this.
  7. zet23t

    zet23t

    Joined:
    Jan 28, 2015
    Posts:
    1
    There is a nice solution to this problem:

    1. Derive your test class from ScriptableObject
    2. Create a public (or serializable) field member to that class
    3. Select the test class in editor
    4. Assign the scriptable object (or whatever you need in your test) to the default values of that script
    This here works when executed - the "HeroA" variable is initialized when the test is run:

    upload_2023-7-4_12-20-59.png