Entitas
Entitas copied to clipboard
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