Skip to content

Commit 63eddf5

Browse files
authored
Merge pull request #7 from AAulicino/feature/custom-assertions
Feature/custom assertions
2 parents f670557 + b6bd67d commit 63eddf5

19 files changed

+628
-10
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [UNRELEASED]
8+
### Added
9+
- MoveNextAndExpect<T>() custom assertion.
10+
- MoveNextAndExpect(object value) custom assertion.
11+
712
## [1.1.0] - 2022-06-16
813
### Added
914
- Nested coroutine calls support.
@@ -12,5 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1217
### Added
1318
- Initial release.
1419

20+
[UNRELEASED]: https://github.com/AAulicino/Unity-Coroutines-for-NSubstitute/compare/1.1.0...UNRELEASED
1521
[1.1.0]: https://github.com/AAulicino/Unity-Coroutines-for-NSubstitute/compare/1.0.0...1.1.0
1622
[1.0.0]: https://github.com/AAulicino/Unity-Coroutines-for-NSubstitute/releases/tag/1.0.0

Editor/Assertions.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using CoroutineSubstitute.Substitutes;
7+
using NUnit.Framework;
8+
using UnityEngine;
9+
10+
namespace CoroutineSubstitute.Assertions
11+
{
12+
public static class CoroutineRunnerAssertions
13+
{
14+
internal static readonly Dictionary<Type, TypeAssert> customTypes = new Dictionary<Type, TypeAssert>();
15+
16+
public static void RegisterCustomType<T> (TypeAssert typeAssert)
17+
{
18+
customTypes[typeof(T)] = typeAssert;
19+
}
20+
21+
public static void MoveNextAndExpect<T> (ICoroutineRunnerSubstitute runner)
22+
{
23+
if (runner.ActiveCoroutines.Count > 1)
24+
{
25+
throw new InvalidOperationException(
26+
"MoveNextAndExpect currently only supports a single Coroutine instance"
27+
);
28+
}
29+
30+
runner.MoveNext();
31+
object current = runner.ActiveCoroutines.Single().Current;
32+
33+
if (current is T)
34+
return;
35+
36+
Assert.Fail($"Expected: {typeof(T).Name}\nBut was: {GetTypeName(current)}");
37+
}
38+
39+
public static void MoveNextAndExpect (ICoroutineRunnerSubstitute runner, object value)
40+
{
41+
if (runner.ActiveCoroutines.Count > 1)
42+
{
43+
throw new InvalidOperationException(
44+
"MoveNextAndExpect currently only supports a single Coroutine instance"
45+
);
46+
}
47+
48+
runner.MoveNext();
49+
object current = runner.ActiveCoroutines.Single().Current;
50+
51+
AssertType(value, current);
52+
53+
switch (value)
54+
{
55+
case WaitForSeconds val:
56+
Assert.AreEqual(GetTotalSeconds(val), GetTotalSeconds(Cast(current, val)));
57+
break;
58+
59+
case WaitForSecondsRealtime val:
60+
Assert.AreEqual(val.waitTime, Cast(current, val).waitTime);
61+
break;
62+
63+
case WaitForEndOfFrame _:
64+
case WaitForFixedUpdate _:
65+
case IEnumerator _:
66+
case null:
67+
Assert.Pass();
68+
break;
69+
70+
default:
71+
if (customTypes.TryGetValue(value.GetType(), out TypeAssert typeAssert))
72+
typeAssert(value, current);
73+
else
74+
throw new NotSupportedException(
75+
$"{GetTypeName(value)} is not supported. You can register your custom types"
76+
+ $" using {nameof(CoroutineSubstitute)}.{nameof(CoroutineSubstitute.RegisterCustomType)}."
77+
);
78+
break;
79+
}
80+
}
81+
82+
static void AssertType (object expected, object actual)
83+
{
84+
if (expected?.GetType() != actual?.GetType())
85+
Assert.Fail($"Expected {GetTypeName(expected)} but got {GetTypeName(actual)}");
86+
}
87+
88+
static string GetTypeName (object obj) => obj?.GetType()?.Name ?? "null";
89+
90+
static T Cast<T> (object value, T _) => (T)value;
91+
92+
static float GetTotalSeconds (WaitForSeconds waitForSeconds)
93+
{
94+
return (float)typeof(WaitForSeconds)
95+
.GetField("m_Seconds", BindingFlags.NonPublic | BindingFlags.Instance)
96+
.GetValue(waitForSeconds);
97+
}
98+
}
99+
}

Editor/Assertions/CoroutineRunnerAssertions.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/CoroutineSubstitute.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
using CoroutineSubstitute.Assertions;
12
using CoroutineSubstitute.Substitutes;
23
using NSubstitute;
34

45
namespace CoroutineSubstitute
56
{
7+
public delegate void TypeAssert (object expected, object actual);
8+
69
public static class CoroutineSubstitute
710
{
811
public static ICoroutineRunner Create ()
912
=> Substitute.ForPartsOf<CoroutineRunnerSubstitute>();
13+
14+
public static void RegisterCustomType<T> (TypeAssert typeAssert)
15+
=> CoroutineRunnerAssertions.RegisterCustomType<T>(typeAssert);
1016
}
1117
}

Editor/ICoroutineRunnerTestExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using CoroutineSubstitute.Assertions;
23
using CoroutineSubstitute.Substitutes;
34

45
namespace CoroutineSubstitute
@@ -8,6 +9,12 @@ public static class ICoroutineRunnerTestExtensions
89
public static bool MoveNext (this ICoroutineRunner runner)
910
=> CastToSubstitute(runner).MoveNext();
1011

12+
public static void MoveNextAndExpect<T> (this ICoroutineRunner runner)
13+
=> CoroutineRunnerAssertions.MoveNextAndExpect<T>(CastToSubstitute(runner));
14+
15+
public static void MoveNextAndExpect (this ICoroutineRunner runner, object value)
16+
=> CoroutineRunnerAssertions.MoveNextAndExpect(CastToSubstitute(runner), value);
17+
1118
static CoroutineRunnerSubstitute CastToSubstitute (ICoroutineRunner runner)
1219
{
1320
if (runner is CoroutineRunnerSubstitute substitute)

Editor/Substitutes/CoroutineRunnerSubstitute.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ namespace CoroutineSubstitute.Substitutes
1010
{
1111
public class CoroutineRunnerSubstitute : ICoroutineRunner, ICoroutineRunnerSubstitute
1212
{
13+
public ICollection<IStartCoroutineCall> ActiveCoroutines => activeCoroutinesDict.Values;
14+
1315
static readonly IStartCoroutineCallFactory defaultCallFactory = new StartCoroutineCallFactory();
1416

17+
readonly Dictionary<int, IStartCoroutineCall> activeCoroutinesDict;
1518
readonly IStartCoroutineCallFactory callFactory;
16-
readonly Dictionary<int, IStartCoroutineCall> activeCoroutines;
1719

1820
public CoroutineRunnerSubstitute () : this(defaultCallFactory)
1921
{
@@ -22,7 +24,7 @@ public CoroutineRunnerSubstitute () : this(defaultCallFactory)
2224
public CoroutineRunnerSubstitute (IStartCoroutineCallFactory callFactory)
2325
{
2426
this.callFactory = callFactory;
25-
activeCoroutines = new Dictionary<int, IStartCoroutineCall>();
27+
activeCoroutinesDict = new Dictionary<int, IStartCoroutineCall>();
2628
}
2729

2830
public virtual Coroutine StartCoroutine (IEnumerator enumerator)
@@ -31,29 +33,29 @@ public virtual Coroutine StartCoroutine (IEnumerator enumerator)
3133
throw new ArgumentNullException(nameof(enumerator));
3234

3335
IStartCoroutineCall call = callFactory.Create(enumerator);
34-
activeCoroutines.Add(call.Id, call);
36+
activeCoroutinesDict.Add(call.Id, call);
3537
return CoroutineFactory.Create(call.Id);
3638
}
3739

3840
public virtual void StopAllCoroutines ()
3941
{
40-
activeCoroutines.Clear();
42+
activeCoroutinesDict.Clear();
4143
}
4244

4345
public virtual void StopCoroutine (Coroutine routine)
4446
{
4547
if (routine == null)
4648
throw new ArgumentNullException(nameof(routine));
4749

48-
activeCoroutines.Remove(routine.GetId());
50+
activeCoroutinesDict.Remove(routine.GetId());
4951
}
5052

5153
public virtual bool MoveNext ()
5254
{
5355
bool anySucceeded = false;
5456
HashSet<int> callsToRemove = new HashSet<int>();
5557

56-
foreach (IStartCoroutineCall call in activeCoroutines.Values.ToArray())
58+
foreach (IStartCoroutineCall call in activeCoroutinesDict.Values.ToArray())
5759
{
5860
bool result = call.MoveNext();
5961

@@ -63,21 +65,21 @@ public virtual bool MoveNext ()
6365
if (call.Current is Coroutine coroutine)
6466
{
6567
int coroutineId = coroutine.GetId();
66-
call.SetNestedCoroutine(activeCoroutines[coroutineId]);
68+
call.SetNestedCoroutine(activeCoroutinesDict[coroutineId]);
6769
callsToRemove.Add(coroutineId);
6870
}
6971
anySucceeded |= result;
7072
}
7173

7274
foreach (int id in callsToRemove)
73-
activeCoroutines.Remove(id);
75+
activeCoroutinesDict.Remove(id);
7476

7577
return anySucceeded;
7678
}
7779

7880
public virtual void Reset ()
7981
{
80-
foreach (IStartCoroutineCall call in activeCoroutines.Values)
82+
foreach (IStartCoroutineCall call in activeCoroutinesDict.Values)
8183
call.Reset();
8284
}
8385
}

Editor/Substitutes/ICoroutineRunnerSubstitute.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
using System.Collections.Generic;
2+
using CoroutineSubstitute.Substitutes.Call;
3+
14
namespace CoroutineSubstitute.Substitutes
25
{
36
public interface ICoroutineRunnerSubstitute : ICoroutineRunner
47
{
8+
ICollection<IStartCoroutineCall> ActiveCoroutines { get; }
59
bool MoveNext ();
610
void Reset ();
711
}

Tests/Editor/Substitute/Assertions.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)