From 8b581a510bd1efaa3c00777c54dc06c66bf43f40 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Fri, 2 Oct 2020 16:08:27 +0100 Subject: [PATCH] Tests! --- .../AsyncSessionExtensions.cs | 102 +++++---- .../Neo4j.Driver.Extensions.xml | 105 +++++++++ Neo4j.Driver.Extensions/RecordExtensions.cs | 23 ++ .../ResultCursorExtensions.cs | 70 +++++- .../AsyncSessionExtensionsTests.cs | 207 +++++++++++++++++- .../RecordExtensionsTests.cs | 85 +++++++ .../ResultCursorExtensionsTests.cs | 177 +++++++++++++-- 7 files changed, 688 insertions(+), 81 deletions(-) diff --git a/Neo4j.Driver.Extensions/AsyncSessionExtensions.cs b/Neo4j.Driver.Extensions/AsyncSessionExtensions.cs index 2e0aa19..d37630f 100644 --- a/Neo4j.Driver.Extensions/AsyncSessionExtensions.cs +++ b/Neo4j.Driver.Extensions/AsyncSessionExtensions.cs @@ -1,67 +1,79 @@ namespace Neo4j.Driver.Extensions { + using System; using System.Collections.Generic; using System.Threading.Tasks; + using EnsureThat; + /// + /// Extension methods for the + /// public static class AsyncSessionExtensions { - public static async Task RunReadTransactionForNodeResults(this IAsyncSession session, string query, object parameters, string identifier) + /// + /// Executes a transaction + /// returning the specified. + /// + /// + /// This should be used with queries returning values, for example: MATCH (n) RETURN n + /// + /// The type to attempt to cast to. This should be a class. + /// The to run the transaction on. + /// The query to execute. + /// The parameters to the query. + /// + /// The identifier to cast into . e.g. if the query is + /// MATCH (n) RETURN n the identifier is n. + /// + /// The results of the query. + public static async Task> RunReadTransactionForObjects(this IAsyncSession session, string query, object parameters, string identifier) where T : new() { - var results = await session.ReadTransactionAsync(async work => - { - var cursor = await work.RunAsync(query, parameters); - var fetched = await cursor.FetchAsync(); - - while (fetched) - { - var node = cursor.Current[identifier].As(); - return node.ToObject(); - } - - return default; - }); + Ensure.That(session).IsNotNull(); + Ensure.That(query).IsNotNullOrWhiteSpace(); + Ensure.That(identifier).IsNotNullOrWhiteSpace(); - return results; + return await session.ReadTransactionAsync(tx => ReadTransactionAsList(tx, query, parameters, cursor => cursor.Current.ToObject(identifier))); } - public static async Task RunReadTransaction(this IAsyncSession session, string query, object parameters, string identifier) - where T : new() + /// + /// Executes a transaction + /// returning the specified. + /// + /// + /// This should be used with queries not returning values, for example: + /// MATCH (n) RETURN n.title AS title + /// + /// The type to attempt to cast to. This should be a class. + /// The to run the transaction on. + /// The query to execute. + /// The parameters to the query. + /// + /// The identifier to cast into . e.g. if the query is + /// MATCH (n) RETURN n.title AS title the identifier is title. + /// + /// The results of the query. + public static async Task> RunReadTransaction(this IAsyncSession session, string query, object parameters, string identifier) { - var results = await session.ReadTransactionAsync(async work => - { - var cursor = await work.RunAsync(query, parameters); - var fetched = await cursor.FetchAsync(); + Ensure.That(session).IsNotNull(); + Ensure.That(query).IsNotNullOrWhiteSpace(); + Ensure.That(identifier).IsNotNullOrWhiteSpace(); - while (fetched) - { - return cursor.Current[identifier].As(); - } - - return default; - }); - - return results; + return await session.ReadTransactionAsync(tx => ReadTransactionAsList(tx, query, parameters, cursor => cursor.GetValue(identifier))); } - public static async Task> RunReadTransactionEnumerable(this IAsyncSession session, string query, object parameters, string identifier) - where T : new() + internal static async Task> ReadTransactionAsList(this IAsyncTransaction tx, string query, object parameters, Func conversionFunction) { - var results = await session.ReadTransactionAsync(async work => + var cursor = await tx.RunAsync(query, parameters); + var fetched = await cursor.FetchAsync(); + var output = new List(); + while (fetched) { - var cursor = await work.RunAsync(query, parameters); - var fetched = await cursor.FetchAsync(); - var output = new List(); - while (fetched) - { - output.Add(cursor.Current[identifier].As()); - fetched = await cursor.FetchAsync(); - } - - return output; - }); + output.Add(conversionFunction(cursor)); + fetched = await cursor.FetchAsync(); + } - return results; + return output; } } } \ No newline at end of file diff --git a/Neo4j.Driver.Extensions/Neo4j.Driver.Extensions.xml b/Neo4j.Driver.Extensions/Neo4j.Driver.Extensions.xml index adfee3e..9dbdb8a 100644 --- a/Neo4j.Driver.Extensions/Neo4j.Driver.Extensions.xml +++ b/Neo4j.Driver.Extensions/Neo4j.Driver.Extensions.xml @@ -4,6 +4,48 @@ Neo4j.Driver.Extensions + + + Extension methods for the + + + + + Executes a transaction + returning the specified. + + + This should be used with queries returning values, for example: MATCH (n) RETURN n + + The type to attempt to cast to. This should be a class. + The to run the transaction on. + The query to execute. + The parameters to the query. + + The identifier to cast into . e.g. if the query is + MATCH (n) RETURN n the identifier is n. + + The results of the query. + + + + Executes a transaction + returning the specified. + + + This should be used with queries not returning values, for example: + MATCH (n) RETURN n.title AS title + + The type to attempt to cast to. This should be a class. + The to run the transaction on. + The query to execute. + The parameters to the query. + + The identifier to cast into . e.g. if the query is + MATCH (n) RETURN n.title AS title the identifier is title. + + The results of the query. + Attempts to convert a to a type defined by . @@ -118,6 +160,11 @@ A filled instance, or default values otherwise. If the is null. + + + Extension methods for the + + Attempts to cast an instance into a . @@ -147,6 +194,16 @@ equivalents. + + + Gets a value from an instance. Throwing a if the property isn't there. + + The to attempt to get the property as. + The instance to pull the property from. + The name of the identifier to get. + The converted or default + Thrown if the property doesn't exist + Gets a value from an instance, by executing @@ -165,6 +222,54 @@ equivalents. + + + Extension methods for the + + + + + Gets a value from the element. + + The to attempt to get the property as. + The instance to pull the property from. + The name of the identifier to get. + The converted or default + + Thrown if the hasn't had + called on it. + + + + + Gets a value from the element. Throwing an exception if the + isn't there. + + The to attempt to get the property as. + The instance to pull the property from. + The name of the identifier to get. + The converted or default + + Thrown if the hasn't had + called on it. + + + + + Gets a value from the element. Throwing an exception if the + isn't there. + + The to attempt to get the property as. + The instance to pull the property from. + The name of the identifier to get. + If true then a will be thrown if the + isn't there, otherwise the default value will be returned. + The converted or default + + Thrown if the hasn't had + called on it. + + Simplifies the while, pairing, allowing a called to just use a diff --git a/Neo4j.Driver.Extensions/RecordExtensions.cs b/Neo4j.Driver.Extensions/RecordExtensions.cs index 3a06d0a..e2a2587 100644 --- a/Neo4j.Driver.Extensions/RecordExtensions.cs +++ b/Neo4j.Driver.Extensions/RecordExtensions.cs @@ -5,6 +5,9 @@ using System.Linq; using EnsureThat; + /// + /// Extension methods for the + /// public static class RecordExtensions { /// @@ -34,6 +37,7 @@ public static class RecordExtensions return obj; } + /// /// Gets a value from an instance. /// @@ -55,6 +59,25 @@ public static T GetValue(this IRecord record, string identifier) : default; } + /// + /// Gets a value from an instance. Throwing a if the property isn't there. + /// + /// The to attempt to get the property as. + /// The instance to pull the property from. + /// The name of the identifier to get. + /// The converted or default + /// Thrown if the property doesn't exist + public static T GetValueStrict(this IRecord record, string identifier) + { + Ensure.That(record).IsNotNull(); + Ensure.That(identifier).IsNotEmptyOrWhiteSpace(); + + if(record.Keys.Contains(identifier)) + return record.Values[identifier].As(); + + throw new KeyNotFoundException($"'{identifier}' doesn't exist on the Record."); + } + /// /// Gets a value from an instance, by executing /// the method via reflection. diff --git a/Neo4j.Driver.Extensions/ResultCursorExtensions.cs b/Neo4j.Driver.Extensions/ResultCursorExtensions.cs index 46ce9b6..f3bbf9b 100644 --- a/Neo4j.Driver.Extensions/ResultCursorExtensions.cs +++ b/Neo4j.Driver.Extensions/ResultCursorExtensions.cs @@ -2,30 +2,71 @@ { using System; using System.Collections.Generic; - using System.Linq; using EnsureThat; + /// + /// Extension methods for the + /// public static class ResultCursorExtensions { + /// + /// Gets a value from the element. + /// + /// The to attempt to get the property as. + /// The instance to pull the property from. + /// The name of the identifier to get. + /// The converted or default + /// + /// Thrown if the hasn't had + /// called on it. + /// public static T GetValue(this IResultCursor cursor, string identifier) { - Ensure.That(cursor).IsNotNull(); - Ensure.That(identifier).IsNotNullOrWhiteSpace(); - - return cursor.Current.Keys.Contains(identifier) - ? cursor.Current[identifier].As() - : default; + return cursor.GetValueInternal(identifier, false); } + /// + /// Gets a value from the element. Throwing an exception if the + /// isn't there. + /// + /// The to attempt to get the property as. + /// The instance to pull the property from. + /// The name of the identifier to get. + /// The converted or default + /// + /// Thrown if the hasn't had + /// called on it. + /// public static T GetValueStrict(this IResultCursor cursor, string identifier) + { + return cursor.GetValueInternal(identifier, true); + } + + /// + /// Gets a value from the element. Throwing an exception if the + /// isn't there. + /// + /// The to attempt to get the property as. + /// The instance to pull the property from. + /// The name of the identifier to get. + /// If true then a will be thrown if the + /// isn't there, otherwise the default value will be returned. + /// The converted or default + /// + /// Thrown if the hasn't had + /// called on it. + /// + private static T GetValueInternal(this IResultCursor cursor, string identifier, bool strict) { Ensure.That(cursor).IsNotNull(); Ensure.That(identifier).IsNotNullOrWhiteSpace(); - if(cursor.Current.Keys.Contains(identifier)) - return cursor.Current[identifier].As(); - - throw new KeyNotFoundException($"'{identifier}' not returned from the query."); + if (cursor.Current == null) + throw new NullReferenceException("The cursor doesn't appear to have had 'FetchAsync' called on it?"); + + return strict + ? cursor.Current.GetValueStrict(identifier) + : cursor.Current.GetValue(identifier); } /// @@ -44,10 +85,13 @@ public static T GetValueStrict(this IResultCursor cursor, string identifier) /// yields the retrieved from the . public static async IAsyncEnumerable GetContent(this IResultCursor cursor, string identifier) { + Ensure.That(cursor).IsNotNull(); + Ensure.That(identifier).IsNotNullOrWhiteSpace(); + var fetched = await cursor.FetchAsync(); while (fetched) { - yield return cursor.Current[identifier].As(); + yield return cursor.GetValue(identifier); fetched = await cursor.FetchAsync(); } } @@ -60,6 +104,8 @@ public static async IAsyncEnumerable GetContent(this IResultCursor cursor, /// yields the retrieved from the . public static async IAsyncEnumerable GetRecords(this IResultCursor cursor) { + Ensure.That(cursor).IsNotNull(); + var fetched = await cursor.FetchAsync(); while (fetched) { diff --git a/Neo4j.Drivers.Extensions.Tests/AsyncSessionExtensionsTests.cs b/Neo4j.Drivers.Extensions.Tests/AsyncSessionExtensionsTests.cs index dc1d56d..18e1871 100644 --- a/Neo4j.Drivers.Extensions.Tests/AsyncSessionExtensionsTests.cs +++ b/Neo4j.Drivers.Extensions.Tests/AsyncSessionExtensionsTests.cs @@ -1,23 +1,111 @@ namespace Neo4j.Drivers.Extensions.Tests { using System; + using System.Collections.Generic; + using System.Linq; using System.Threading.Tasks; using FluentAssertions; + using Moq; + using Neo4j.Driver; using Neo4j.Driver.Extensions; using Xunit; public class AsyncSessionExtensionsTests { - public class RunReadTransactionForNodeResultsMethod + public class RunReadTransactionForObjectsMethod { [Fact] public async Task ThrowsArgumentNullException_WhenSessionIsNull() { - var ex = await Assert.ThrowsAsync(async () => await AsyncSessionExtensions.RunReadTransactionForNodeResults(null, "query", null, "identifier")); + var ex = await Assert.ThrowsAsync(async () => await AsyncSessionExtensions.RunReadTransactionForObjects(null, "query", null, "identifier")); ex.Should().NotBeNull(); } + + [Fact] + public async Task ThrowsArgumentNullException_WhenQueryIsNull() + { + var mock = new Mock(); + var ex = await Assert.ThrowsAsync(async () => await mock.Object.RunReadTransactionForObjects(null, null, "identifier")); + ex.Should().NotBeNull(); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public async Task ThrowsArgumentException_WhenQueryIsEmptyOrWhitespace(string query) + { + var mock = new Mock(); + var ex = await Assert.ThrowsAsync(async () => await mock.Object.RunReadTransactionForObjects(query, null, "identifier")); + ex.Should().NotBeNull(); + } + + [Fact] + public async Task ThrowsArgumentNullException_WhenIdentifierIsNull() + { + var mock = new Mock(); + var ex = await Assert.ThrowsAsync(async () => await mock.Object.RunReadTransactionForObjects("query", null, null)); + ex.Should().NotBeNull(); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public async Task ThrowsArgumentException_WhenIdentifierIsEmptyOrWhitespace(string identifier) + { + var mock = new Mock(); + var ex = await Assert.ThrowsAsync(async () => await mock.Object.RunReadTransactionForObjects("query", null, identifier)); + ex.Should().NotBeNull(); + } + + [Fact] + public async Task CallsReadTransactionAsync() + { + var mock = new Mock(); + await mock.Object.RunReadTransactionForObjects("query", null, "id"); + mock.Verify(x => x.ReadTransactionAsync(It.IsAny>>>()), Times.Once); + } + + [Fact] + public async Task GetsResults() + { + const string identifier = "foo"; + const string expectedStringProperty = "string"; + + var mockSession = new Mock(); + var mockTransaction = new Mock(); + var mockCursor = new Mock(); + var mockRecord = new Mock(); + + mockRecord.Setup(x => x.Keys).Returns(new List { identifier }); + mockRecord.Setup(x => x[identifier]).Returns(new Dictionary + { + {nameof(Foo.StringProperty), expectedStringProperty} + }); + mockCursor.Setup(x => x.FetchAsync()).Returns(Task.FromResult(true)) + .Callback(() => + { + mockCursor.Reset(); + mockCursor.Setup(x => x.Current).Returns(mockRecord.Object); + mockCursor.Setup(x => x.FetchAsync()).Returns(Task.FromResult(false)); + }); + + mockTransaction + .Setup(x => x.RunAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockCursor.Object)); + + List callBackResponse = null; + mockSession + .Setup(x => x.ReadTransactionAsync(It.IsAny>>>())) + .Callback(async (Func>> func) => callBackResponse = await func(mockTransaction.Object)); + + await mockSession.Object.RunReadTransactionForObjects("Query", null, identifier); + callBackResponse.Should().NotBeNull(); + callBackResponse.Should().HaveCount(1); + callBackResponse[0].StringProperty.Should().Be(expectedStringProperty); + } } + public class RunReadTransactionMethod { [Fact] @@ -26,15 +114,122 @@ public async Task ThrowsArgumentNullException_WhenSessionIsNull() var ex = await Assert.ThrowsAsync(async () => await AsyncSessionExtensions.RunReadTransaction(null, "query", null, "identifier")); ex.Should().NotBeNull(); } + + [Fact] + public async Task ThrowsArgumentNullException_WhenQueryIsNull() + { + var mock = new Mock(); + var ex = await Assert.ThrowsAsync(async () => await mock.Object.RunReadTransaction(null, null, "identifier")); + ex.Should().NotBeNull(); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public async Task ThrowsArgumentException_WhenQueryIsEmptyOrWhitespace(string query) + { + var mock = new Mock(); + var ex = await Assert.ThrowsAsync(async () => await mock.Object.RunReadTransaction(query, null, "identifier")); + ex.Should().NotBeNull(); + } + + [Fact] + public async Task ThrowsArgumentNullException_WhenIdentifierIsNull() + { + var mock = new Mock(); + var ex = await Assert.ThrowsAsync(async () => await mock.Object.RunReadTransaction("query", null, null)); + ex.Should().NotBeNull(); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public async Task ThrowsArgumentException_WhenIdentifierIsEmptyOrWhitespace(string identifier) + { + var mock = new Mock(); + var ex = await Assert.ThrowsAsync(async () => await mock.Object.RunReadTransaction("query", null, identifier)); + ex.Should().NotBeNull(); + } + + [Fact] + public async Task CallsReadTransactionAsync() + { + var mock = new Mock(); + await mock.Object.RunReadTransaction("query", null, "id"); + mock.Verify(x => x.ReadTransactionAsync(It.IsAny>>>()), Times.Once); + } + + [Fact] + public async Task ReturnsCorrectValues() + { + const string identifier = "foo"; + const string expectedString = "string"; + + var mockSession = new Mock(); + var mockTransaction = new Mock(); + var mockCursor = new Mock(); + var mockRecord = new Mock(); + + mockRecord.Setup(x => x.Keys).Returns(new List { identifier }); + mockRecord.Setup(x => x.Values[identifier]).Returns(expectedString); + mockCursor.Setup(x => x.FetchAsync()).Returns(Task.FromResult(true)) + .Callback(() => + { + mockCursor.Reset(); + mockCursor.Setup(x => x.Current).Returns(mockRecord.Object); + mockCursor.Setup(x => x.FetchAsync()).Returns(Task.FromResult(false)); + }); + + mockTransaction + .Setup(x => x.RunAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockCursor.Object)); + + List callBackResponse = null; + mockSession + .Setup(x => x.ReadTransactionAsync(It.IsAny>>>())) + .Callback(async (Func>> func) => callBackResponse = await func(mockTransaction.Object)); + + await mockSession.Object.RunReadTransaction("Query", null, identifier); + callBackResponse.Should().NotBeNull(); + callBackResponse.Should().HaveCount(1); + callBackResponse[0].Should().Be(expectedString); + } } - public class RunReadTransactionEnumerableMethod + + public class ReadTransactionAsListTMethod { [Fact] - public async Task ThrowsArgumentNullException_WhenSessionIsNull() + public async Task Works() { - var ex = await Assert.ThrowsAsync(async () => await AsyncSessionExtensions.RunReadTransactionEnumerable(null, "query", null, "identifier")); - ex.Should().NotBeNull(); + const string identifier = "foo"; + const string expectedString = "string"; + + var mockTransaction = new Mock(); + var mockCursor = new Mock(); + var mockRecord = new Mock(); + + mockRecord.Setup(x => x.Keys).Returns(new List { identifier }); + mockRecord.Setup(x => x.Values[identifier]).Returns(expectedString); + mockCursor.Setup(x => x.Current).Returns(mockRecord.Object); + mockCursor.Setup(x => x.FetchAsync()).Returns(Task.FromResult(true)) + .Callback(() => + { + mockCursor.Reset(); + mockCursor.Setup(x => x.FetchAsync()).Returns(Task.FromResult(false)); + }); + + mockTransaction + .Setup(x => x.RunAsync(It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockCursor.Object)); + + + var results = await mockTransaction.Object.ReadTransactionAsList("Query", null, cursor => expectedString); + results.Should().NotBeNull(); + + var resultsList = results.ToList(); + resultsList.Should().HaveCount(1); + resultsList[0].Should().Be(expectedString); } } } diff --git a/Neo4j.Drivers.Extensions.Tests/RecordExtensionsTests.cs b/Neo4j.Drivers.Extensions.Tests/RecordExtensionsTests.cs index b9f3a97..c4bfbb4 100644 --- a/Neo4j.Drivers.Extensions.Tests/RecordExtensionsTests.cs +++ b/Neo4j.Drivers.Extensions.Tests/RecordExtensionsTests.cs @@ -98,6 +98,91 @@ public void ReturnsDefault_WhenIdentifierNotThere() var stringResult = mock.Object.GetValue("not-there"); stringResult.Should().Be(default); } + + [Fact] + public void ReturnsCorrectValue() + { + const string stringIdentifier = "foo"; + const string intIdentifier = "bar"; + const string expectedStringValue = "string"; + const int expectedIntValue = 42; + + var mock = new Mock(); + + + mock.Setup(x => x.Keys).Returns(new List { stringIdentifier, intIdentifier }); + mock.Setup(x => x.Values).Returns(new Dictionary + { + {stringIdentifier, expectedStringValue}, + {intIdentifier, expectedIntValue} + }); + + mock.Object.GetValue(intIdentifier).Should().Be(expectedIntValue); + mock.Object.GetValue(stringIdentifier).Should().Be(expectedStringValue); + } + } + + public class GetValueStrictT + { + [Fact] + public void ThrowsArgumentNullException_WhenRecordIsNull() + { + var ex = Assert.Throws(() => RecordExtensions.GetValueStrict(null, "identifier")); + ex.Should().NotBeNull(); + } + + [Fact] + public void ThrowsArgumentNullException_WhenIdentifierIsNull() + { + var mock = new Mock(); + + var ex = Assert.Throws(() => mock.Object.GetValueStrict(null)); + ex.Should().NotBeNull(); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public void ThrowsArgumentException_WhenIdentifierIsNull(string identifier) + { + var mock = new Mock(); + + var ex = Assert.Throws(() => mock.Object.GetValueStrict(identifier)); + ex.Should().NotBeNull(); + } + + [Fact] + public void ReturnsDefault_WhenIdentifierNotThere() + { + var mock = new Mock(); + mock.Setup(x => x.Keys).Returns(new List()); + mock.Setup(x => x.Values).Returns(new Dictionary()); + + var ex = Assert.Throws(() => mock.Object.GetValueStrict("not-there")); + ex.Should().NotBeNull(); + } + + [Fact] + public void ReturnsCorrectValue() + { + const string stringIdentifier = "foo"; + const string intIdentifier = "bar"; + const string expectedStringValue = "string"; + const int expectedIntValue = 42; + + var mock = new Mock(); + + + mock.Setup(x => x.Keys).Returns(new List{stringIdentifier, intIdentifier}); + mock.Setup(x => x.Values).Returns(new Dictionary + { + {stringIdentifier, expectedStringValue}, + {intIdentifier, expectedIntValue} + }); + + mock.Object.GetValueStrict(intIdentifier).Should().Be(expectedIntValue); + mock.Object.GetValueStrict(stringIdentifier).Should().Be(expectedStringValue); + } } } } \ No newline at end of file diff --git a/Neo4j.Drivers.Extensions.Tests/ResultCursorExtensionsTests.cs b/Neo4j.Drivers.Extensions.Tests/ResultCursorExtensionsTests.cs index 8b1d060..1316e43 100644 --- a/Neo4j.Drivers.Extensions.Tests/ResultCursorExtensionsTests.cs +++ b/Neo4j.Drivers.Extensions.Tests/ResultCursorExtensionsTests.cs @@ -1,6 +1,7 @@ namespace Neo4j.Drivers.Extensions.Tests { using System; + using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; using Moq; @@ -13,6 +14,15 @@ public class ResultCursorExtensionsTests { public class GetValueTMethod { + [Fact] + public void ThrowsNullReferenceException_WhenCurrentIsNull() + { + var mock = new Mock(); + mock.Setup(x => x.Current).Returns((IRecord) null); + var ex = Assert.Throws(() => mock.Object.GetValue("foo")); + ex.Should().NotBeNull(); + } + [Fact] public void ThrowsArgumentNullException_WhenCursorIsNull() { @@ -42,25 +52,61 @@ public void ThrowsArgumentException_WhenIdentifierIsEmptyOrWhitespace(string ide [Fact] public void ThrowsFormatException_WhenAttemptingToCastIncorrectly() { - //i.e. a 'string' to an int. - throw new NotImplementedException(); + const string identifier = "foo"; + var mock = new Mock(); + var mockRecord = new Mock(); + mockRecord.Setup(x => x.Keys).Returns(new List {identifier}); + mockRecord.Setup(x => x.Values[identifier]).Returns("string"); + mock.Setup(x => x.Current).Returns(mockRecord.Object); + + var ex = Assert.Throws(() => mock.Object.GetValue(identifier)); + ex.Should().NotBeNull(); } [Fact] public void ReturnsCorrectValue() { - throw new NotImplementedException(); + const string stringIdentifier = "foo"; + const string intIdentifier = "bar"; + const string expectedStringValue = "string"; + const int expectedIntValue = 42; + + var mock = new Mock(); + var mockRecord = new Mock(); + mockRecord.Setup(x => x.Keys).Returns(new List {stringIdentifier, intIdentifier}); + mockRecord.Setup(x => x.Values[stringIdentifier]).Returns(expectedStringValue); + mockRecord.Setup(x => x.Values[intIdentifier]).Returns(expectedIntValue); + mock.Setup(x => x.Current).Returns(mockRecord.Object); + + mock.Object.GetValue(stringIdentifier).Should().Be(expectedStringValue); + mock.Object.GetValue(intIdentifier).Should().Be(expectedIntValue); } [Fact] public void ReturnsDefaultValue_WhenCurrentDoesNotHaveIdentifier() { - throw new NotImplementedException(); + const string stringIdentifier = "foo"; + const string intIdentifier = "bar"; + + var mock = new Mock(); + mock.Setup(x => x.Current.Keys).Returns(new List()); + + mock.Object.GetValue(stringIdentifier).Should().Be(default); + mock.Object.GetValue(intIdentifier).Should().Be(default); } } public class GetValueStrictTMethod { + [Fact] + public void ThrowsNullReferenceException_WhenCurrentIsNull() + { + var mock = new Mock(); + mock.Setup(x => x.Current).Returns((IRecord) null); + var ex = Assert.Throws(() => mock.Object.GetValueStrict("foo")); + ex.Should().NotBeNull(); + } + [Fact] public void ThrowsArgumentNullException_WhenCursorIsNull() { @@ -75,7 +121,7 @@ public void ThrowsArgumentNullException_WhenIdentifierIsNull() var ex = Assert.Throws(() => mock.Object.GetValueStrict(null)); ex.Should().NotBeNull(); } - + [Theory] [InlineData("")] [InlineData(" ")] @@ -89,22 +135,47 @@ public void ThrowsArgumentException_WhenIdentifierIsEmptyOrWhitespace(string ide [Fact] public void ThrowsFormatException_WhenAttemptingToCastIncorrectly() { - //i.e. a 'string' to an int. - throw new NotImplementedException(); + const string identifier = "foo"; + var mock = new Mock(); + var mockRecord = new Mock(); + mockRecord.Setup(x => x.Keys).Returns(new List {identifier}); + mockRecord.Setup(x => x.Values[identifier]).Returns("string"); + mock.Setup(x => x.Current).Returns(mockRecord.Object); + + var ex = Assert.Throws(() => mock.Object.GetValueStrict(identifier)); + ex.Should().NotBeNull(); } [Fact] public void ReturnsCorrectValue() { - throw new NotImplementedException(); + const string stringIdentifier = "foo"; + const string intIdentifier = "bar"; + const string expectedStringValue = "string"; + const int expectedIntValue = 42; + + var mock = new Mock(); + var mockRecord = new Mock(); + mockRecord.Setup(x => x.Keys).Returns(new List {stringIdentifier, intIdentifier}); + mockRecord.Setup(x => x.Values[stringIdentifier]).Returns(expectedStringValue); + mockRecord.Setup(x => x.Values[intIdentifier]).Returns(expectedIntValue); + mock.Setup(x => x.Current).Returns(mockRecord.Object); + + mock.Object.GetValueStrict(stringIdentifier).Should().Be(expectedStringValue); + mock.Object.GetValueStrict(intIdentifier).Should().Be(expectedIntValue); } [Fact] public void ThrowsKeyNotFoundException_WhenCurrentDoesNotHaveIdentifier() { - throw new NotImplementedException(); - } + const string stringIdentifier = "foo"; + + var mock = new Mock(); + mock.Setup(x => x.Current.Keys).Returns(new List()); + var ex = Assert.Throws(() => mock.Object.GetValueStrict(stringIdentifier).Should().Be(default)); + ex.Should().NotBeNull(); + } } public class GetContentTMethod @@ -114,7 +185,9 @@ public async Task ThrowsArgumentNullException_WhenCursorIsNull() { var ex = await Assert.ThrowsAsync(async () => { - await foreach (var _ in ResultCursorExtensions.GetContent(null, "identifier")) { } + await foreach (var _ in ResultCursorExtensions.GetContent(null, "identifier")) + { + } }); ex.Should().NotBeNull(); @@ -124,9 +197,11 @@ public async Task ThrowsArgumentNullException_WhenCursorIsNull() public async Task ThrowsArgumentNullException_WhenIdentifierIsNull() { var mock = new Mock(); - var ex = await Assert.ThrowsAsync(async () => + var ex = await Assert.ThrowsAsync(async () => { - await foreach (var _ in mock.Object.GetContent(null)) { } + await foreach (var _ in mock.Object.GetContent(null)) + { + } }); ex.Should().NotBeNull(); @@ -140,11 +215,58 @@ public async Task ThrowsArgumentException_WhenIdentifierIsEmptyOrWhitespace(stri var mock = new Mock(); var ex = await Assert.ThrowsAsync(async () => { - await foreach (var _ in mock.Object.GetContent(identifier)) { } + await foreach (var _ in mock.Object.GetContent(identifier)) + { + } }); ex.Should().NotBeNull(); } + + [Fact] + public async Task ThrowsFormatException_WhenIdentifierCanNotBeCastToTheTypeGiven() + { + const string identifier = "foo"; + const string expectedString = "string"; + + var mock = new Mock(); + var mockRecord = new Mock(); + mockRecord.Setup(x => x.Keys).Returns(new List { identifier }); + mockRecord.Setup(x => x.Values[identifier]).Returns(expectedString); + + mock.Setup(x => x.Current).Returns(mockRecord.Object); + mock.Setup(x => x.FetchAsync()).Returns(Task.FromResult(true)); + + var ex = await Assert.ThrowsAsync(async () => + { + await foreach (var _ in mock.Object.GetContent(identifier)) + { + } + }); + ex.Should().NotBeNull(); + } + + [Fact] + public async Task YieldsAllTheItems_WhenTheyCanBeConvertedToT() + { + const string identifier = "foo"; + const string expectedString = "string"; + + var mock = new Mock(); + var mockRecord = new Mock(); + mockRecord.Setup(x => x.Keys).Returns(new List { identifier }); + mockRecord.Setup(x => x.Values[identifier]).Returns(expectedString); + + mock.Setup(x => x.Current).Returns(mockRecord.Object); + mock.Setup(x => x.FetchAsync()).Returns(Task.FromResult(true)); + + await foreach (var item in mock.Object.GetContent(identifier)) + { + item.Should().Be(expectedString); + mock.Reset(); + mock.Setup(x => x.FetchAsync()).Returns(Task.FromResult(false)); + } + } } public class GetRecordsMethod @@ -152,14 +274,33 @@ public class GetRecordsMethod [Fact] public async Task ThrowsArgumentNullException_WhenCursorIsNull() { - var ex = await Assert.ThrowsAsync(async () => { - await foreach (var _ in ResultCursorExtensions.GetRecords(null)) {} + var ex = await Assert.ThrowsAsync(async () => + { + await foreach (var _ in ResultCursorExtensions.GetRecords(null)) + { + } }); ex.Should().NotBeNull(); } - - } + [Fact] + public async Task WillYieldAllTheRecords() + { + var mock = new Mock(); + var mockRecord = new Mock(); + mock.Setup(x => x.FetchAsync()).Returns(Task.FromResult(true)); + mock.Setup(x => x.Current).Returns(mockRecord.Object); + int count = 0; + await foreach (var _ in mock.Object.GetRecords()) + { + count++; + mock.Reset(); + mock.Setup(x => x.FetchAsync()).Returns(Task.FromResult(false)); + } + + count.Should().Be(1); + } + } } } \ No newline at end of file