Skip to content

Commit 2131130

Browse files
authored
Merge pull request #8 from AAulicino/develop
1.2.0
2 parents 41dcc7b + 80432b5 commit 2131130

25 files changed

+668
-17
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+
## [1.2.0] - 2022-07-03
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+
[1.2.0]: https://github.com/AAulicino/Unity-Coroutines-for-NSubstitute/compare/1.1.0...1.2.0
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
}

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
Unity Coroutines for NSubstitute
22
========
3+
[![Tests](https://github.com/AAulicino/Unity-Coroutines-for-NSubstitute/actions/workflows/main.yml/badge.svg)](https://github.com/AAulicino/Unity-Coroutines-for-NSubstitute/actions/workflows/main.yml)
4+
5+
- [Unity Coroutines for NSubstitute](#unity-coroutines-for-nsubstitute)
6+
* [What is it?](#what-is-it)
7+
* [Installation](#installation)
8+
* [Basic use](#basic-use)
9+
+ [Creating the mock](#creating-the-mock)
10+
+ [Using the mock](#using-the-mock)
11+
+ [Custom Assertions](#custom-assertions)
312

413
## What is it?
514

@@ -73,7 +82,7 @@ Let's use this simple counter class for testing:
7382
while (true)
7483
{
7584
Current++;
76-
yield return null;
85+
yield return new WaitForSeconds(1);
7786
}
7887
}
7988
}
@@ -116,4 +125,28 @@ public class GameSetup : MonoBehaviour, ICoroutineRunner
116125
}
117126
```
118127

128+
### Custom Assertions
129+
130+
You can also assert the yielded values from the coroutine:
131+
132+
```csharp
133+
ICoroutineRunner runner = CoroutineSubstitute.Create();
134+
Counter counter = new Counter(Runner);
135+
136+
Runner.MoveNextAndExpect<WaitForSeconds>();
137+
```
138+
139+
To assert the amount of seconds configured in the `WaitForSeconds` object:
140+
```csharp
141+
ICoroutineRunner runner = CoroutineSubstitute.Create();
142+
Counter counter = new Counter(Runner);
143+
144+
Runner.MoveNextAndExpect(new WaitForSeconds(1));
145+
```
146+
147+
119148
Other samples can be found in the [Samples](https://github.com/AAulicino/Unity-Coroutines-for-NSubstitute/tree/main/Tests/Editor/Samples) folder.
149+
150+
---
151+
152+
You can also have a look at the [SystemTests](https://github.com/AAulicino/Unity-Coroutines-for-NSubstitute/tree/main/Tests/Editor/SystemTests) folder as the tests found in there represent some real uses cases.

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)