How to deal with methods only running in the Unity scope?
Hi,
I have a have a question. I set up my Unity project and another solution for tests. I want to test my initialize system which makes use of the method UnityEngine.Random.Range.
When running the unit test in my xUnit project the test fails with the following exception
System.Security.SecurityException ECall methods must be packaged into a system module. at UnityEngine.Random.Range(Int32 minInclusive, Int32 maxExclusive)
I don't want to modify the Unity project because the code works fine...
How did you solve this?
Hello,
I'd wrap Random calls into RandomService:IRandomService, and then mock IRandomService with NSubstitute during tests
The only option you have is to substitue/mock the logic. Random (https://github.com/Unity-Technologies/UnityCsReference/blob/61f92bd79ae862c4465d35270f9d1d57befd1761/Runtime/Export/Random/Random.bindings.cs) is part of the Untiy C++ Engine and can't be accesses outside of it. To see which parts can be accessed if you link the UnityEngine.dll see the Repo of Unity: https://github.com/Unity-Technologies/UnityCsReference
Everything that has no extern call can be used (eg implemented directly in c#).
@c0ffeeartc @rglobig thanks for your help. The part above was easy but now I tried to get into the visual part and want to create GameObjects in the scene. I followed this guide
https://github.com/FNGgames/Entitas-Simple-Movement-Unity-Example#addviewsystem
and created this reactive system
public sealed class AddFieldViewSystem : ReactiveSystem<GameEntity>
{
private readonly Transform _fieldViewContainer;
public AddFieldViewSystem(Contexts contexts) : base(contexts.Game)
{
GameObject fieldViewContainer = new GameObject("Fields");
_fieldViewContainer = fieldViewContainer.transform;
}
protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
=> context.CreateCollector(GameMatcher.FieldIndex);
protected override bool Filter(GameEntity entity)
=> entity.HasFieldIndex && !entity.HasView;
protected override void Execute(List<GameEntity> entities)
{
foreach (GameEntity gameEntity in entities)
{
Vector2Int fieldIndex = gameEntity.FieldIndex.value;
GameObject fieldGameObject = new GameObject($"({fieldIndex.x}|{fieldIndex.y})");
fieldGameObject.transform.SetParent(_fieldViewContainer, false);
gameEntity.AddView(fieldGameObject);
fieldGameObject.Link(gameEntity);
}
}
}
Is this even testable from outside? How would you mock all the things?
How would you mock all the things?
Curious myself, don't know a better way. Maybe not to test some parts...
@c0ffeeartc yes, the same like IRandomService :)
For the SetParent method I think this mock implementation should do it
go.transform.parent = otherTransform
but what else needs to get mocked here? The tests also crash when commenting the whole line out. I think the tests can't deal with GameObjects ... ? Because they live in the scene ... ? (But maybe they can, because it's just an object instance from a type ... )
No Unity related method here but it still crashes
public sealed class AddFieldViewSystem : ReactiveSystem<GameEntity>
{
private readonly Transform _fieldViewContainer;
public AddFieldViewSystem(Contexts contexts) : base(contexts.Game)
{
GameObject fieldViewContainer = new GameObject("Fields");
_fieldViewContainer = fieldViewContainer.transform;
}
protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
=> context.CreateCollector(GameMatcher.FieldIndex);
protected override bool Filter(GameEntity entity)
=> entity.HasFieldIndex && !entity.HasView;
protected override void Execute(List<GameEntity> entities)
{
foreach (GameEntity gameEntity in entities)
{
Vector2Int fieldIndex = gameEntity.FieldIndex.value;
GameObject fieldGameObject = new GameObject($"({fieldIndex.x}|{fieldIndex.y})");
// ... removed for testing purposes ...
}
}
}
I think the tests can't deal with GameObjects ... ? Because they live in the scene ... ? (But maybe they can, because it's just an object instance from a type ... )
As @rglobig mentioned - Unity related parts don't always work in external tests because they are part of the Untiy C++ Engine.
Removing using UnityEngine from C# script would make IDE to highlight errors of all(or most) Unity related parts.
To find a line that crashes test comment code inside constructor and Execute methods, then uncomment them line by line and run test each time until crash.
Some fix options are:
- Humble Object pattern
- interacting with View through wrapper
- writing Unity dependent tests with Unity's NUnit test framework
I personally just wrote tests with NUnit, but then stopped writing them because ServiceLocator pattern in my project made life tougher for testing.
@c0ffeeartc thanks. I thought this would be a basic problem faced by many people and maybe solved by many people :S
but I will give this a try
https://github.com/sschmid/Entitas-CSharp/wiki/How-I-build-games-with-Entitas-(FNGGames)#view-layer-abstraction
I tried to minimize my system
public sealed class AddFieldViewSystem : ReactiveSystem<GameEntity>
{
public AddFieldViewSystem(Contexts contexts) : base(contexts.Game)
{
}
protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
=> context.CreateCollector(GameMatcher.FieldIndex);
protected override bool Filter(GameEntity entity)
=> entity.HasFieldIndex && !entity.HasView;
protected override void Execute(List<GameEntity> entities)
{
foreach (GameEntity gameEntity in entities)
{
GameObject fieldGameObject = new GameObject();
gameEntity.AddView(fieldGameObject);
fieldGameObject.Link(gameEntity);
}
}
}
and created a minimized test
public class Foo
{
[Fact]
private void Bar()
{
// Throws 'System.Security.SecurityException: ECall methods must be packaged into a system module.'
GameObject fieldGameObject = new GameObject();
Assert.True(true);
}
}
I would not try to mock View Systems. This is not simulation/game logic code and therefore it's imo not necessary to test it outside of unity. I would write tests inside the view e.g. Unity with NUnit to test logic like this.
Logic that requires Unity to run can be tested using https://docs.unity3d.com/Manual/testing-editortestsrunner.html
