kentcb / pclmock Goto Github PK
View Code? Open in Web Editor NEWA simple mocking framework in a PCL.
License: MIT License
A simple mocking framework in a PCL.
License: MIT License
Consider a means of recursively applying plugins. For example, consider the following interface:
public interface ISomeInterface
{
IImmutableList<string> Stuff { get; }
IObservable<IImmutableList<string>> GetMoreStuff();
}
The collections plugin will ensure the Stuff
property defaults to ImmutableList<string>.Empty
. The observables plugin will ensure GetMoreStuff
returns Observable.Return(default(IImmutableList<string>))
. But that default value will be null
. If plugins were instead applied recursively (at the discretion of each plugin), it could ensure that GetMoreStuff
returned Observable.Return(ImmutableList<string>.Empty)
by default, which would be far more useful.
The serious challenge here is that the IPlugin
interface is not designed to be fine-grained. It allows plugins to inject any code into the ConfigureBehavior
and ConfigureLooseBehavior
methods of a mock. As a consequence, each plugin is generating this.When
calls for its own specific scenario. Designing a plugin interface that still allows a high degree of control by plugins, but also allows them to recursively invoke other plugins for fine-grained situations will be difficult.
When configuring expectations for Akavache's IBlobCache
, I did something like this:
this
.When(x => x.GetCreatedAt(It.IsAny<IEnumerable<string>>()))
.Return(Observable.Return<Dictionary<string, DateTimeOffset?>>(null));
But this resulted in an exception:
System.NullReferenceException: Object reference not set to an instance of an object.
at PCLMock.MockBase`1.GetContinuationKey(LambdaExpression selector)
at PCLMock.MockBase`1.EnsureWhenContinuationCollection(LambdaExpression memberSelector)
at PCLMock.MockBase`1.AddOrReplaceWhenContinuation(LambdaExpression memberSelector, WhenContinuation continuation)
at PCLMock.MockBase`1.When[TMember](Expression`1 selector)
There are times when it is useful to be able to have mocks for third party interfaces. The code generator does not currently provide a means of doing so. Perhaps it could also traverse third party assemblies and feed them into the generation pipeline.
I'm coming up against this exception for some test cases I'm trying to write. I'm deploying this to Xamarin with the xUnit device runner package. It's unclear to me why I'm receiving this exception when I don't think I'm chaining any methods together. Could you point me in the right direction as to what I'm doing wrong, or what I'm trying to do that you don't support?
I'm trying to setup a method that is marked as async Task<ApiResult<ApiResponse>>
-- I have not seen any examples in your docs that support this behavior but this compiles just fine.
Help me understand what's going on here?
public class TestClassMock : MockBase<ITestClass>, ITestClass
{
public TestClassMock(MockBehavior behavior = MockBehavior.Loose)
: base(behavior)
{
if (behavior == MockBehavior.Loose)
{
var result = Task.FromResult(
new ApiResult<ApiResponse>
{
HttpStatusCode = HttpStatusCode.OK,
Status = ApiCallStatus.Success,
ResultData = new ApiResponse
{
SomeArray = new[]
{
new PocoObject { SomeString = "12345abcdef" }
}
}
});
this.When(x => this.RegisterAsync(It.IsAny<ApiRequest>(), It.IsAny<INetworkSettings>())) //Exception is thrown on this line
.Return(result);
}
}
public async Task<ApiResult<ApiResponse>> RegisterAsync(ApiRequest apiRequestData, INetworkSettings networkSettings = null)
{
return await this.Apply(x => x.RegisterAsync(apiRequestData, networkSettings));
}
}
Per #18, give some thought to whether it is possible+desirable to open up the loose behavior "pipeline" to consuming code. That is, allow client code to make sweeping decisions about loose behavior such as "for any method returning Task<T>
, return Task.FromResult(default(T))
by default".
ISomethingSomething
) in codegen assembly (also: consider an altogether separate SDK assembly)PCLMock.CodeGeneration
that includes only the codegen assemblyThe failure messages reported when verifications are not met can result in confusion because they do not always contain sufficient information. Examples of good failure messages are:
Normally in Moq, when I want to validate that a method was called with a complex object that has a certain property matched, I would do something like:
object.Verify(x => x.Method(It.Is(x => x.Property == "someValueIExpect")), Times.Once)
In this library, it seems like the only way to achieve this type of behavior is with the .Do()
method and setting a local boolean there. However, when I try to do this in a method that uses async/await, I get a NullReferenceException thrown.
Consider the example library code:
public interface ITestClass
{
Task<ApiResult<ApiResponse>> RegisterAsync(ApiRequest apiRequestData);
ApiResult<ApiResponse> Register(ApiRequest apiRequestData);
}
public class ApiRequest
{
public Guid Id { get; set; }
}
public class ApiResponse
{
public PocoObject[] SomeArray { get; set; }
}
public class PocoObject
{
public string SomeString { get; set; }
}
public class ApiResult<TResponseType>
{
public HttpStatusCode HttpStatusCode { get; set; }
public ApiCallStatus Status { get; set; }
public TResponseType ResultData { get; set; }
}
public enum ApiCallStatus
{
Success,
Failure
}
And the following Mock and XUnit Tests:
public class TestClassMock : MockBase<ITestClass>, ITestClass
{
public TestClassMock(MockBehavior behavior = MockBehavior.Loose)
: base(behavior)
{
}
public async Task<ApiResult<ApiResponse>> RegisterAsync(ApiRequest apiRequestData)
{
return await this.Apply(x => x.RegisterAsync(apiRequestData)); //Null Reference Exception thrown here in the first test
}
public ApiResult<ApiResponse> Register(ApiRequest apiRequestData)
{
return this.Apply(x => x.Register(apiRequestData));
}
}
public class UnitTests
{
[Fact]
public async Task TestRegisterAsync() //Fails :(
{
var mock = new TestClassMock();
var id = Guid.NewGuid();
var calledWithId = false;
mock.When(x => x.RegisterAsync(It.IsAny<ApiRequest>())).Do((ApiRequest req) => calledWithId = req.Id == id);
var result = await mock.RegisterAsync(new ApiRequest { Id = id });
mock.Verify(x => x.RegisterAsync(It.IsAny<ApiRequest>()));
Assert.True(calledWithId);
}
[Fact]
public async Task TestRegisterAsync_WithoutTest() //Success!
{
var mock = new TestClassMock();
var id = Guid.NewGuid();
mock.When(x => x.RegisterAsync(It.IsAny<ApiRequest>())).Return(
Task.FromResult(new ApiResult<ApiResponse>
{
HttpStatusCode = HttpStatusCode.OK,
ResultData = new ApiResponse
{
SomeArray = new[]
{
new PocoObject() { SomeString = "ABC123" }
}
}
}));
var result = await mock.RegisterAsync(new ApiRequest { Id = id });
mock.Verify(x => x.RegisterAsync(It.IsAny<ApiRequest>()));
Assert.Equal("ABC123", result.ResultData.SomeArray.First().SomeString);
}
[Fact]
public async Task TestRegister() //Success!
{
var mock = new TestClassMock();
var id = Guid.NewGuid();
var calledWithId = false;
mock.When(x => x.Register(It.IsAny<ApiRequest>()))
.Do((ApiRequest req) => calledWithId = req.Id == id);
mock.Register(new ApiRequest { Id = id });
mock.Verify(x => x.Register(It.IsAny<ApiRequest>()));
Assert.True(calledWithId);
}
}
When I run this in Xamarin.iOS test runner, The first test that has a .Do() (async/await) fails while second test without a .Do() (async) is successful, and the third test with a .Do() is also successful.
It seems that there is a bug with async/await and registering a .Do() in that this null reference exception is thrown.
I created a minimal reproduction of the issue here, requires Xamarin (tested on visual studio): http://cl.ly/2T2P3u3n361t
Attempt to write a T4 template that uses Roslyn to code-gen mock implementations. Would probably need to be driven by some kind of input file telling it which types to generate mocks for.
V5 of PCLMock will (hopefully) be netstandard/netcore. This is a checklist of stuff I need to do/problems I need to solve for that to happen.
All these notes are old, but just keeping them around in case I need to refer to them.
Here is a useful reference on creating a NuGet package for the console runner.
Another useful reference with respect to executing T4 templates in an SDK-style csproj.
UPDATE (24/05/18): had another crack at this using Buildalyzer and am giving up in frustration again. I was able to move the code base in the right direction (reinstating tests, for example), but ultimately wasn't able to progress with the actual problem. I need to record this because I'll otherwise forget. These are the problems I'm noticing:
Project
instances. Indeed, that looks like it might be the case. On a hunch, I tried doing a dotnet restore
against the target project before running the generator again, but this results in an exception (see below)System.ArgumentException
HResult=0x80070057
Message=An item with the same key has already been added. Key: Microsoft.CodeAnalysis.CSharp.CSharpCompilationReference
Source=System.Private.CoreLib
StackTrace:
at System.ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(Object key)
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.<FinalizeCompilationAsync>d__30.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.<BuildCompilationInfoFromScratchAsync>d__24.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.<GetOrBuildCompilationInfoAsync>d__22.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.CodeAnalysis.SolutionState.CompilationTracker.<GetCompilationSlowAsync>d__19.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at PCLMock.CodeGeneration.Generator.<>c__DisplayClass3_0.<<GenerateMocksAsync>b__0>d.MoveNext() in C:\Users\kent\Repository\PCLMock\Src\PCLMock.CodeGeneration\Generator.cs:line 77
UPDATE (06/07/18): I've successfully reproduced the above problem and submitted a bug report here.
A verification along the lines of:
mock
.Verify(x => x.SomeCall(It.Matches(y => y.SomeNullableThing.HasValue)))
.WasCalledExactlyOnce();
results in an exception:
System.Reflection.TargetException: Non-static method requires a target
A simple workaround in this specific example is to change to:
mock
.Verify(x => x.SomeCall(It.Matches(y => y.SomeNullableThing != null)))
.WasCalledExactlyOnce();
NOTE: tested on mono only (Xamarin/iOS).
Check whether it's possible for PCLMock to support both SL and WP platforms.
Events are not currently mocked for reasons explained in the documentation.
Consider ways in which this might be supported, and whether it's worth it. Also think about whether a half-way approach is a Good Idea.
A mock for an internal interface should be generated as internal class but it is generated as public class instead.
Example:
MyAssembly defines:
internal interface IService
{
void DoSomething();
}
I made the interfaces visible for my TestAssembly with [InternalsVisibleFor("MyAssembly.Tests"]
.
Now I aspect that the generated mock class looks the following:
in my MyAssembly.Tests assembly:
[System.CodeDom.Compiler.GeneratedCode("PCLMock", "4.0.0.0")]
[System.Runtime.CompilerServices.CompilerGenerated]
internal partial class ServiceMock : global::PCLMock.MockBase<global::MyAssembly.IService>, global::MyAssembly.IService
{
public ServiceMock (global::PCLMock.MockBehavior behavior = global::PCLMock.MockBehavior.Strict): base (behavior)
{
if ((behavior) == (global::PCLMock.MockBehavior.Loose))
{
ConfigureLooseBehavior();
}
}
partial void ConfigureLooseBehavior();
...
}
Since they are not generated as internal I got an Inconsistent accessibility
error.
Thanks for your help!
Best regards,
Paul
It seems the WithStatements
method in VB's syntax generator does not work correctly. No statements are being added to the method.
This works as expected:
var mock = new SomeMock();
mock.When(x => x.SomeMethod("foo")).Return("bar");
Assert.Equal("bar", mock.SomeMethod("foo"));
This does not:
var mock = new SomeMock();
var obj = new object();
mock.When(x => x.SomeMethod(obj)).Return("bar");
Assert.Equal("bar", mock.SomeMethod(obj));
There is no message assigned to the exception thrown by the parameterless overload of WhenContinuation.Throw()
. This results in an InvalidOperationException
with no message, which can be confusing when debugging failing unit tests.
Add an appropriate error message. Perhaps "Mock has been configured to throw when accessing this member."
When trying to use the console runner as a pre build event the resulting mocks.g.cs file can not resolve all types, as it omits the global:: keyword. Furthermore it also sometimes omits generic types in Tasks. When using the t4 template it works perfectly.
The Configuration.FromXDocument
method includes a call to Cast
. This can fail when comments are interspersed amongst the XML elements. OfType
should be used instead.
public interface IFirst
{
void Foo();
}
public interface ISecond
{
void Foo();
}
public interface IAggregate : IFirst, ISecond
{
}
This will generate an invalid mock implementation of IAggregate
because it will have two Foo
methods. Not sure how best to address this. Obviously at least one implementation would need to be explicit, but which? Or perhaps the generator should recognise this situation and just skip the generation of Foo
altogether, leaving it to the user to disambiguate...
Add a verbose/logging switch to the console generator so it's obvious what decisions it is making.
This would be beneficial for projects that are strongly signed and would like to utilize this library.
In case of projects for tests, they need to be signed when testing signed library with [InternalsVisibleTo] attribute set.
Hi, developers!
I use PCLMockCodeGen.exe tool for generate mocks. When code was generated I found the next error in my Mocks.cs file:
public System.Double ToDisplayX(System.Double x)
{
return this.Apply(x => x.ToDisplayX(x));
}
As you can see, PCLMockCodeGen tool generate param in anonymous method with "x" name, but my function argument already have "x" name. In this case Visual Studio shows error and this code can not be compiled.
The code generators are not working on Mac/Xamarin/mono. It's likely this is due to Roslyn not being incorporated into that stack yet. Try again when that work is done and investigate further as necessary.
Add an It.Matches(Func<T, bool>)
method. The function is called to determine whether the argument matches or not, thus giving the client code utmost flexibility. An example of usage:
var mock = ...;
mock.When(x => x.SomeMethod(It.Matches(i => i % 2 == 0)))
.Return("Even");
mock.When(x => x.SomeMethod(It.Matches(i => i % 2 == 1)))
.Return("Odd");
Assert.Equal("Even", mock.SomeMethod(2));
Assert.Equal("Even", mock.SomeMethod(223710));
Assert.Equal("Odd", mock.SomeMethod(1));
Assert.Equal("Odd", mock.SomeMethod(223713));
Hi, developers!
I use PCLMockCodeGen.exe tool for generate mocks. When code was generated I found that tool not generated code for event in my interface:
public interface IProductLicenseService
{
event LicenseChangedEventHandler LicenseChanged;
}
Improve the evaluation behavior significantly. In addition to what is currently possible, the following should be possible:
// calling a method (FromSeconds) to obtain the value
mock.Verify(x => x.SomeMethod(TimeSpan.FromSeconds(1)))
.WasCalledExactlyOnce();
// performing calculations
mock.Verify(x => x.SomeMethod((37 * 2) / 12))
.WasCalledExactlyOnce();
Of course, the same should be true in the context of a When()
call.
I'm trying to verify that a navigation method on the NavigationService
is called exactly once with the following code:
var navigationService = new NavigationServiceMock();
var viewModel = new CatalogPageViewModel(navigationService);
// Call
viewModel.CreateNewItemCommand.Execute(null);
var item = GetLatestCreatedItem();
var parameters = new NavigationParameters();
parameters.Add("id", item.Id);
// Verify
navigationService.Verify(x => x.NavigateAsync("EditPage", parameters)).WasCalledExactlyOnce();
However it yields the following exception:
PCLMock.VerificationException : Verification that NavigateAsync(It.Is("EditPage"), It.Is(?=id=89003df7-36a0-4319-8299-1195f3b7b5de [Prism.Navigation.NavigationParameters])) was called exactly once failed because it was called 0 times.
at
PCLMock.VerifyContinuation.ThrowVerificationException (System.String format, System.Object[] args) [0x00011] in
....
I've added breakpoints and the NavigateAsync sure is being hit with the exact same parameters, so I assume it fails because it expects the parameters
to be an exact object and not another instance of the same class.
I would like to verify that a NavigationParameters
is passed to the NavigateAsync
and that the NavigationParameters
should have a id
key with the value of the Item.Id
.
Is this possible?
set
-only properties are currently unsupported, for reasons explained in the documentation.
Think about possible alternatives and whether they're worth it.
It seems that property expressions are somewhat different running under mono so are causing issues for the visitors. Example:
mock
.When(x => x.SomeProperty)
.Return(1);
This is currently failing because the visitor is unable to extract the requisite information from the expression.
I just tried to use the T4 generator, but when I try to run the custom tool, I get following error:
Error 16 Running transformation: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.AggregateException: One or more errors occurred. ---> System.IO.FileNotFoundException: Could not load file or assembly 'System.Composition.TypedParts, Version=1.0.27.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
at Microsoft.CodeAnalysis.Host.Mef.MefHostServices.Create(IEnumerable`1 assemblies)
at Microsoft.CodeAnalysis.Host.Mef.DesktopMefHostServices.get_DefaultServices()
at Kent.Boogaart.PCLMock.CodeGeneration.Generator.<GenerateMocksAsync>d__0.MoveNext() in C:\Users\kent\Repository\PCLMock\Src\Kent.Boogaart.PCLMock.CodeGeneration\Generator.cs:line 24
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Kent.Boogaart.PCLMock.CodeGeneration.XmlBasedGenerator.<GenerateMocksAsync>d__2.MoveNext() in C:\Users\kent\Repository\PCLMock\Src\Kent.Boogaart.PCLMock.CodeGeneration\XmlBasedGenerator.cs:line 48
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
at System.Threading.Tasks.Task`1.get_Result()
at Kent.Boogaart.PCLMock.CodeGeneration.XmlBasedGenerator.GenerateMocks(String solutionPath, String xmlPath, Language language) in C:\Users\kent\Repository\PCLMock\Src\Kent.Boogaart.PCLMock.CodeGeneration\XmlBasedGenerator.cs:line 29
at Kent.Boogaart.PCLMock.CodeGeneration.XmlBasedGenerator.GenerateMocks(String solutionPath, String xmlPath, String language) in C:\Users\kent\Repository\PCLMock\Src\Kent.Boogaart.PCLMock.CodeGeneration\XmlBasedGenerator.cs:line 21
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Microsoft.VisualStudio.TextTemplating59389E350AC879AC2313AB3BF88249987D0426CB576DCFE492173DC732D3844164099E1D5EA5CD146FE3DD173ED128E47A71AAFFE3474AF69A3196EB6CD0079A.GeneratedTextTransformation.TransformText() in c:\Src\Strat\ConsoleApplication5\UnitTestProject1\Mocks.tt:line 19 c:\Src\Strat\ConsoleApplication5\UnitTestProject1\Mocks.tt 19 1 UnitTestProject1
It's .net 4.5 project
Hi,
I found two annoying issues when mocking a method to throw an exception:
bool called = false;
mock.When(x => x.SomeMethod()).Do(() => called = true).Throw()
try { mock.SomeMethod(); }
catch { }
mock.When(x => x.SomeMethod()).Do(() => throw new MyException())
try { mock.SomeMethod(); }
catch (MyException) { /* Will never go there. */ }
catch (TargetInvocationException) { /* Will go there instead. */ }
Thanks for all the nice work!
The code generator is generating empty setter for indexer properties that do not have setters.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.