Skip to content

Commit

Permalink
Merge pull request #131 from ColmBhandal/feature/pre-access-wrapper
Browse files Browse the repository at this point in the history
Feature/pre access wrapper
  • Loading branch information
ColmBhandal authored Jan 31, 2022
2 parents 0e91111 + 829ece4 commit af6fd32
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added general-purpose Sparse Array type for any amount of dimensions with builder and some basic functions (#108)
- Added sparse 2D array type for 2D-specific operations (#120)
- Added a shift operator for sparse arrays (#122)
- Added a pre-access wrapper, which wraps an object and ensures an action is run before any access to that object (#131_

### Added
- Support for writing a 1D one-based array to a 2D one-based array (#9)
Expand Down
3 changes: 3 additions & 0 deletions CsharpExtras/Api/CsharpExtrasApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,8 @@ public ISparseArrayBuilder<TVal> NewSparseArrayBuilder<TVal>(PositiveInteger dim

public ISparseArray2DBuilder<TVal> NewSparseArray2DBuilder<TVal>(TVal defaultValue) =>
new SparseArray2DBuilderImpl<TVal>(defaultValue, this);

public IPreAccessWrapper<TObj> NewPreAccessWrapper<TObj>(TObj obj, Action<TObj> preAccessAction) =>
new PreAccessWrapperImpl<TObj>(obj, preAccessAction);
}
}
9 changes: 9 additions & 0 deletions CsharpExtras/Api/ICsharpExtrasApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ public interface ICsharpExtrasApi
IComparer<T> NewDescendingComparer<T>();
IDirectoryDecorator NewDirectoryDecorator();
IValidator<T> NewEmptyValidator<T>();

/// <summary>
/// Creates a new pre-access wrapper, containing a wrapped object and a pre-access action to run before every access to that object
/// </summary>
/// <typeparam name="TObj">The object type</typeparam>
/// <param name="obj">An object instance which will be wrapped</param>
/// <param name="preAccessAction">An action to run before any access to the given object</param>
/// <returns>A new pre-access wrapper object</returns>
IPreAccessWrapper<TObj> NewPreAccessWrapper<TObj>(TObj obj, Action<TObj> preAccessAction);
IFileDecorator NewFileDecorator();
IIntegerLeaf NewIntegerLeaf(int payload);
IIntegerTree NewIntegerTree(int payload);
Expand Down
25 changes: 25 additions & 0 deletions CsharpExtras/Event/Wrapper/IPreAccessWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace CsharpExtras.Event.Wrapper
{
/// <summary>
/// Encapsulates access to some object, so that accessing that object is always preceded by a pre-access action
/// </summary>
/// <typeparam name="TObj"></typeparam>
public interface IPreAccessWrapper<TObj>
{
/// <summary>
/// Gets the result of applying some function to the object
/// </summary>
/// <typeparam name="TReturn">The return type of the function</typeparam>
/// <param name="f">The function to apply</param>
/// <returns>The result of the function</returns>
TReturn Get<TReturn>(Func<TObj, TReturn> f);

/// <summary>
/// Runs the given action on the object
/// </summary>
/// <param name="act">The action to run on the object</param>
void Run(Action<TObj> act);
}
}
30 changes: 30 additions & 0 deletions CsharpExtras/Event/Wrapper/PreAccessWrapperImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace CsharpExtras.Event.Wrapper
{
internal class PreAccessWrapperImpl<TObj> : IPreAccessWrapper<TObj>
{
private readonly TObj _object;
private readonly Action<TObj> _preAccessAction;

public PreAccessWrapperImpl(TObj obj, Action<TObj> preAccessAction)
{
_object = obj;
_preAccessAction = preAccessAction ?? throw new ArgumentNullException(nameof(preAccessAction));
}

public void Run(Action<TObj> act)
{
_preAccessAction(_object);
act(_object);
}

public TReturn Get<TReturn>(Func<TObj, TReturn> f)
{
_preAccessAction(_object);
return f(_object);
}
}
}
157 changes: 157 additions & 0 deletions CsharpExtrasTest/Event/Wrapper/PreAccessWrapperTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using CsharpExtras.Api;
using CsharpExtras.Event.Wrapper;
using Moq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;

namespace CsharpExtrasTest.Event.Wrapper
{
[TestFixture, Category("Unit")]
internal class PreAccessWrapperTest
{
private ICsharpExtrasApi Api { get; } = new CsharpExtrasApi();

[Test]
public void GIVEN_Wrapper_WHEN_Get_THEN_ExpectedValueReturned()
{
//Arrange
Mock<IEventHandler> mockHandler = new Mock<IEventHandler>();
Mock<ITestObj> mockObj = new Mock<ITestObj>();
Mock<ITestObj> mockOtherObj = new Mock<ITestObj>();
Action<ITestObj> preAccessAction = o => mockHandler.Object.Handle(o);
Mock<ITestGetter<ITestObj>> mockGetter = new Mock<ITestGetter<ITestObj>>();

mockHandler.Setup(x => x.Handle(It.IsAny<ITestObj>()));
mockGetter.Setup(g => g.Get(mockObj.Object)).Returns(mockOtherObj.Object);

IPreAccessWrapper<ITestObj> wrapper
= Api.NewPreAccessWrapper(mockObj.Object, preAccessAction);

//Act
ITestObj obj = wrapper.Get(mockGetter.Object.Get);

//Assert
Assert.AreSame(mockOtherObj.Object, obj);
}

[Test]
public void GIVEN_Wrapper_WHEN_Get_THEN_AllOperationsRunInExpectedOrder()
{
//Arrange
Mock<IEventHandler> mockHandler = new Mock<IEventHandler>();
Mock<ITestObj> mockObj = new Mock<ITestObj>();
Mock<ITestObj> mockOtherObj = new Mock<ITestObj>();
Action<ITestObj> preAccessAction = o => mockHandler.Object.Handle(o);
Mock<ITestGetter<ITestObj>> mockGetter = new Mock<ITestGetter<ITestObj>>();


//Used to verify order of operations. See: https://stackoverflow.com/a/10609506/5134722
int callOrder = 0;
mockHandler.Setup(x => x.Handle(It.IsAny<ITestObj>()))
.Callback(() => Assert.AreEqual(0, callOrder++)).Verifiable();
mockGetter.Setup(g => g.Get(mockObj.Object)).Returns(mockOtherObj.Object)
.Callback(() => Assert.AreEqual(1, callOrder++)).Verifiable();

IPreAccessWrapper<ITestObj> wrapper
= Api.NewPreAccessWrapper(mockObj.Object, preAccessAction);

//Act
wrapper.Get(mockGetter.Object.Get);

//Assert
mockObj.Verify();
mockHandler.Verify();
}

[Test]
public void GIVEN_Wrapper_WHEN_Run_THEN_AllOperationsRunInExpectedOrder()
{
//Arrange
Mock<IEventHandler> mockHandler = new Mock<IEventHandler>();
Mock<ITestObj> mockObj = new Mock<ITestObj>();
Action<ITestObj> preAccessAction = o => mockHandler.Object.Handle(o);


//Used to verify order of operations. See: https://stackoverflow.com/a/10609506/5134722
int callOrder = 0;
mockHandler.Setup(x => x.Handle(It.IsAny<ITestObj>()))
.Callback(() => Assert.AreEqual(0, callOrder++)).Verifiable();
mockObj.Setup(o => o.Foo())
.Callback(() => Assert.AreEqual(1, callOrder++)).Verifiable();

IPreAccessWrapper<ITestObj> wrapper
= Api.NewPreAccessWrapper(mockObj.Object, preAccessAction);

//Act
wrapper.Run(o => o.Foo());

//Assert
mockObj.Verify();
mockHandler.Verify();
}

[Test]
public void GIVEN_PreAccessException_WHEN_Run_THEN_ExceptionThrownAndActionNotExecuted()
{
//Arrange
Mock<IEventHandler> mockHandler = new Mock<IEventHandler>();
Mock<ITestObj> mockObj = new Mock<ITestObj>();
Action<ITestObj> preAccessAction = o => mockHandler.Object.Handle(o);

mockHandler.Setup(x => x.Handle(It.IsAny<ITestObj>()))
.Throws<ArgumentException>();
mockObj.Setup(o => o.Foo()).Verifiable();

IPreAccessWrapper<ITestObj> wrapper
= Api.NewPreAccessWrapper(mockObj.Object, preAccessAction);

//Act
Assert.Throws<ArgumentException>(() => wrapper.Run(o => o.Foo()));

//Assert
mockObj.Verify(o => o.Foo(), Times.Never());
}

[Test]
public void GIVEN_PreAccessException_WHEN_Get_THEN_ExceptionThrownAndGetNotExecuted()
{
//Arrange
Mock<IEventHandler> mockHandler = new Mock<IEventHandler>();
Mock<ITestObj> mockObj = new Mock<ITestObj>();
Mock<ITestObj> mockOtherObj = new Mock<ITestObj>();
Action<ITestObj> preAccessAction = o => mockHandler.Object.Handle(o);
Mock<ITestGetter<ITestObj>> mockGetter = new Mock<ITestGetter<ITestObj>>();

mockHandler.Setup(x => x.Handle(It.IsAny<ITestObj>()))
.Throws<ArgumentException>();
mockGetter.Setup(g => g.Get(mockObj.Object)).Returns(mockOtherObj.Object)
.Verifiable();

IPreAccessWrapper<ITestObj> wrapper
= Api.NewPreAccessWrapper(mockObj.Object, preAccessAction);

//Act
Assert.Throws<ArgumentException>(() => wrapper.Get(mockGetter.Object.Get));

//Assert
mockGetter.Verify(g => g.Get(It.IsAny<ITestObj>()), Times.Never());
}
}

interface ITestGetter<TResult>
{
TResult Get(ITestObj obj);
}

interface ITestObj
{
void Foo();
}

interface IEventHandler
{
void Handle(ITestObj obj);
}
}
20 changes: 20 additions & 0 deletions CsharpExtrasTest/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// In SDK-style projects such as this one, several assembly attributes that were historically
// defined in this file are now automatically added during build and populated with
// values defined in project properties. For details of which attributes are included
// and how to customise this process see: https://aka.ms/assembly-info-properties


// Setting ComVisible to false makes the types in this assembly not visible to COM
// components. If you need to access a type in this assembly from COM, set the ComVisible
// attribute to true on that type.

[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM.

[assembly: Guid("7909326e-9e35-48ed-9e25-4b74d183b0a0")]

[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

0 comments on commit af6fd32

Please sign in to comment.