How to use MatcherInfo class of Telerik.JustMock.Plugins package

Best JustMockLite code snippet using Telerik.JustMock.Plugins.MatcherInfo

Run JustMockLite automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

MatcherInfo.cs

Source: MatcherInfo.cs Github

copy
1/*
2 JustMock Lite
3 Copyright © 2021 Progress Software Corporation
4
5   Licensed under the Apache License, Version 2.0 (the "License");
6   you may not use this file except in compliance with the License.
7   You may obtain a copy of the License at
8
9   http://www.apache.org/licenses/LICENSE-2.0
10
11   Unless required by applicable law or agreed to in writing, software
12   distributed under the License is distributed on an "AS IS" BASIS,
13   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   See the License for the specific language governing permissions and
15   limitations under the License.
16*/
17
18using System;
19using System.Linq;
20using System.Reflection;
21using Telerik.JustMock.Core;
22using Telerik.JustMock.Core.MatcherTree;
23
24namespace Telerik.JustMock.Plugins
25{
26    public class MatcherInfo
27    {
28        public enum MatcherKind
29        {
30            Unknown,
31            Any,
32            Value,
33            Type,
34            NullOrEmpty,
35            Range,
36            Predicate,
37            Params
38        }
39
40        public MatcherKind Kind { get; private set; }
41        public Type ArgType { get; private set; }
42        public int ArgPosition { get; private set; }
43        public string ArgName { get; private set; }
44        public bool IsParamsArg { get; private set; }
45        public string ExpressionString { get; private set; }
46
47        private MatcherInfo(MatcherKind kind, Type argType, int argPosition, string argName, string expressionString)
48        {
49            this.Kind = kind;
50            this.ArgType = argType;
51            this.ArgPosition = argPosition;
52            this.ArgName = argName;
53            this.ExpressionString = expressionString;
54        }
55
56        public static MatcherInfo FromMatcherAndParamInfo(object matcherObject, ParameterInfo paramInfo, out MatcherInfo[] paramsMatchers)
57        {
58            var kind = MatcherKind.Unknown;
59            var argType = typeof(void);
60            var expressionString = "n/a";
61            paramsMatchers = null;
62
63            ITypedMatcher typedMatcher;
64            if (MockingUtil.TryGetAs(matcherObject, out typedMatcher))
65            {
66                if (matcherObject.GetType() == typeof(ValueMatcher))
67                {
68                    kind = MatcherKind.Value;
69                }
70                else if (matcherObject.GetType() == typeof(TypeMatcher))
71                {
72                    kind = MatcherKind.Type;
73                }
74                else if (matcherObject.GetType() == typeof(StringNullOrEmptyMatcher))
75                {
76                    kind = MatcherKind.NullOrEmpty;
77                }
78                else if (matcherObject.GetType() == typeof(RangeMatcher<>).MakeGenericType(typedMatcher.Type))
79                {
80                    kind = MatcherKind.Range;
81                }
82                else if (matcherObject.GetType() == typeof(PredicateMatcher<>).MakeGenericType(typedMatcher.Type))
83                {
84                    kind = MatcherKind.Predicate;
85                }
86
87                if (kind != MatcherKind.Unknown)
88                {
89                    IMatcher matcher;
90                    if (MockingUtil.TryGetAs(matcherObject, out matcher))
91                    {
92                        argType =
93                            typedMatcher.Type != null
94                                ?
95                                typedMatcher.Type
96                                :
97                                paramInfo.ParameterType.IsArray
98                                    ?
99                                        paramInfo.ParameterType.GetElementType()
100                                        :
101                                        paramInfo.ParameterType;
102                        expressionString = matcher.DebugView;
103                    }
104                }
105            }
106            else if (matcherObject.GetType() == typeof(ParamsMatcher))
107            {
108                IContainerMatcher containerMatcher;
109                if (MockingUtil.TryGetAs<IContainerMatcher>(matcherObject, out containerMatcher))
110                {
111                    kind = MatcherKind.Params;
112                    paramsMatchers = containerMatcher.Matchers.Select(
113                        contained =>
114                            {
115                                MatcherInfo[] dummy;
116                                var result = FromMatcherAndParamInfo(contained, paramInfo, out dummy);
117                                result.IsParamsArg = true;
118                                return result;
119                            })
120                        .ToArray();
121                }
122            }
123            else if (matcherObject.GetType() == typeof(AnyMatcher))
124            {
125                kind = MatcherKind.Any;
126            }
127
128            return new MatcherInfo(kind, argType, paramInfo.Position, paramInfo.Name, expressionString);
129        }
130    }
131}
132
Full Screen

IDebugWindowPlugin.cs

Source: IDebugWindowPlugin.cs Github

copy
1/*
2 JustMock Lite
3 Copyright © 2020 - 2021 Progress Software Corporation
4
5   Licensed under the Apache License, Version 2.0 (the "License");
6   you may not use this file except in compliance with the License.
7   You may obtain a copy of the License at
8
9   http://www.apache.org/licenses/LICENSE-2.0
10
11   Unless required by applicable law or agreed to in writing, software
12   distributed under the License is distributed on an "AS IS" BASIS,
13   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   See the License for the specific language governing permissions and
15   limitations under the License.
16*/
17
18using System;
19using System.Reflection;
20using Telerik.JustMock.AutoMock.Ninject.Modules;
21using Telerik.JustMock.Core;
22
23#if !PORTABLE
24namespace Telerik.JustMock.Plugins
25{
26    internal interface ITraceEventsPublisher
27    {
28        void TraceMessage(string message);
29    }
30
31    internal interface IMockRepositoryEventsPublisher
32    {
33        void MockCreated(int repositoryId, string repositoryPath, MockInfo mock, MatcherInfo[] argumentMatchers);
34        void MockInvoked(int repositoryId, string repositoryPath, MockInfo mock, InvocationInfo invocation);
35        void MockUpdated(int repositoryId, string repositoryPath, MockInfo mock, MatcherInfo[] argumentMatchers);
36        void RepositoryCreated(int repositoryId, string repositoryPath, MethodMockInfo methodInfo);
37        void RepositoryRetired(int repositoryId, string repositoryPath);
38    }
39
40    internal interface IDebugWindowPlugin : ITraceEventsPublisher, IMockRepositoryEventsPublisher, INinjectModule, IDisposable
41    {
42
43    }
44}
45#endif
46
Full Screen

MocksRepository.cs

Source: MocksRepository.cs Github

copy
1/*
2 JustMock Lite
3 Copyright © 2010-2015,2018-2019,2021-2022 Progress Software Corporation
4
5   Licensed under the Apache License, Version 2.0 (the "License");
6   you may not use this file except in compliance with the License.
7   You may obtain a copy of the License at
8
9     http://www.apache.org/licenses/LICENSE-2.0
10
11   Unless required by applicable law or agreed to in writing, software
12   distributed under the License is distributed on an "AS IS" BASIS,
13   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   See the License for the specific language governing permissions and
15   limitations under the License.
16*/
17
18using System;
19using System.Collections.Generic;
20using System.Linq;
21using System.Linq.Expressions;
22using System.Reflection;
23using System.Runtime.CompilerServices;
24using System.Text;
25using System.Threading;
26using System.Threading.Tasks;
27using Telerik.JustMock.Core.Behaviors;
28using Telerik.JustMock.Core.Context;
29using Telerik.JustMock.Core.MatcherTree;
30using Telerik.JustMock.Core.Recording;
31using Telerik.JustMock.Core.TransparentProxy;
32using Telerik.JustMock.Diagnostics;
33using Telerik.JustMock.Expectations;
34#if DEBUG
35using Telerik.JustMock.Helpers;
36#endif
37#if !PORTABLE
38using Telerik.JustMock.Plugins;
39#endif
40
41#if NETCORE
42using Debug = Telerik.JustMock.Diagnostics.JMDebug;
43#else
44using Debug = System.Diagnostics.Debug;
45#endif
46
47namespace Telerik.JustMock.Core
48{
49    /// <summary>
50    /// An implementation detail. Not intended for external usage.
51    /// </summary>
52    public sealed class MocksRepository
53    {
54        private static readonly IMockFactory mockFactory;
55        private static readonly List<KeyValuePair<object, IMockMixin>> externalMixinDatabase = new List<KeyValuePair<object, IMockMixin>>();
56        private static readonly Dictionary<object, IMockMixin> externalReferenceMixinDatabase = new Dictionary<object, IMockMixin>(ByRefComparer<object>.Instance);
57
58        private static int repositoryCounter;
59
60        private readonly int repositoryId;
61        private readonly Thread creatingThread;
62        private readonly Dictionary<Type, IMockMixin> staticMixinDatabase = new Dictionary<Type, IMockMixin>();
63        private readonly Dictionary<MethodBase, MethodInfoMatcherTreeNode> arrangementTreeRoots = new Dictionary<MethodBase, MethodInfoMatcherTreeNode>();
64        private readonly Dictionary<MethodBase, MethodInfoMatcherTreeNode> invocationTreeRoots = new Dictionary<MethodBase, MethodInfoMatcherTreeNode>();
65        private readonly Dictionary<KeyValuePair<object, object>, object> valueStore = new Dictionary<KeyValuePair<object, object>, object>();
66        private readonly HashSet<Type> arrangedTypes = new HashSet<Type>();
67        private readonly HashSet<Type> disabledTypes = new HashSet<Type>();
68        private readonly HashSet<MethodBase> globallyInterceptedMethods = new HashSet<MethodBase>();
69
70        private readonly RepositorySharedContext sharedContext;
71        private readonly MocksRepository parentRepository;
72        private readonly List<WeakReference> controlledMocks = new List<WeakReference>();
73
74        private bool isRetired;
75
76        internal static readonly HashSet<Type> KnownUnmockableTypes = new HashSet<Type>
77            {
78                typeof(ValueType),
79                typeof(Enum),
80                typeof(Delegate),
81                typeof(MulticastDelegate),
82                typeof(Array),
83                typeof(String),
84                typeof(IntPtr),
85                typeof(UIntPtr),
86                typeof(void),
87#if !PORTABLE
88                typeof(AppDomain),
89                typeof(TypedReference),
90                typeof(RuntimeArgumentHandle),
91                Type.GetType("System.ContextBoundObject"),
92                Type.GetType("System.ArgIterator"),
93#endif
94#if !SILVERLIGHT
95                Type.GetType("System.__ComObject"),
96#endif
97#if SILVERLIGHT
98                typeof(WeakReference),
99#endif
100            };
101
102        internal IRecorder Recorder
103        {
104            get { return this.sharedContext.Recorder; }
105        }
106
107        internal bool IsRetired
108        {
109            get
110            {
111                return this.isRetired
112                    || (this.parentRepository != null && this.parentRepository.IsRetired)
113#if !PORTABLE
114 || !this.creatingThread.IsAlive
115#endif
116;
117            }
118            set
119            {
120                this.isRetired = value;
121            }
122        }
123
124        internal bool IsParentToAnotherRepository { get; private set; }
125
126        internal MethodBase Method { get; private set; }
127
128        internal DynamicProxyInterceptor Interceptor { get; private set; }
129
130        internal List<IMatcher> MatchersInContext { get; private set; }
131
132        internal int RepositoryId { get { return this.repositoryId; } }
133
134        static MocksRepository()
135        {
136#if !COREFX
137            var badApples = new[]
138            {
139                typeof(System.Security.Permissions.SecurityAttribute),
140                typeof(System.Runtime.InteropServices.MarshalAsAttribute),
141                typeof(object).Assembly.GetType("System.Runtime.InteropServices.TypeIdentifierAttribute"),
142            };
143
144            foreach (var unmockableAttr in badApples.Where(t => t != null))
145                AttributesToAvoidReplicating.Add(unmockableAttr);
146#endif
147
148#if !PORTABLE
149            mockFactory = new DynamicProxyMockFactory();
150#else
151            mockFactory = new StaticProxy.StaticProxyMockFactory();
152#endif
153
154            ProfilerInterceptor.Initialize();
155
156#if DEBUG
157            if (ProfilerInterceptor.IsProfilerAttached)
158            {
159                var logLevelEnvVar = Environment.GetEnvironmentVariable("JUSTMOCK_LOG_LEVEL");
160                LogLevel logLevel;
161                if (Enum.TryParse(logLevelEnvVar, out logLevel))
162                {
163                    DefaultLogLevel.Value = logLevel;
164                }
165            }
166#endif
167        }
168
169        internal MocksRepository(MocksRepository parentRepository, MethodBase method)
170        {
171            this.repositoryId = Interlocked.Increment(ref repositoryCounter);
172            this.Method = method;
173            this.creatingThread = Thread.CurrentThread;
174            this.Interceptor = new DynamicProxyInterceptor(this);
175            this.MatchersInContext = new List<IMatcher>();
176            if (parentRepository != null)
177            {
178                this.parentRepository = parentRepository;
179                this.sharedContext = parentRepository.sharedContext;
180                parentRepository.IsParentToAnotherRepository = true;
181                CopyConfigurationFromParent();
182            }
183            else
184            {
185                this.sharedContext = new RepositorySharedContext();
186            }
187
188            ProfilerInterceptor.IsInterceptionEnabled = true;
189            DebugView.TraceEvent(IndentLevel.Configuration, () => String.Format("Created mocks repository #{0} for {1}", this.repositoryId, this.Method));
190        }
191
192        internal static IMockMixin GetMockMixin(object obj, Type objType)
193        {
194            IMockMixin asMixin = GetMockMixinFromAnyMock(obj);
195            if (asMixin != null)
196            {
197                return asMixin;
198            }
199
200            if (obj != null && objType == null)
201            {
202                objType = obj.GetType();
203            }
204
205            if (obj != null)
206            {
207                asMixin = GetMixinFromExternalDatabase(obj, objType);
208            }
209            else if (objType != null)
210            {
211                MocksRepository repo = MockingContext.ResolveRepository(UnresolvedContextBehavior.CreateNewContextual);
212                if (repo != null)
213                {
214                    lock (repo.staticMixinDatabase)
215                        repo.staticMixinDatabase.TryGetValue(objType, out asMixin);
216                }
217            }
218            return asMixin;
219        }
220
221        private static IMockMixin GetMockMixinFromAnyMock(object mock)
222        {
223            var asMixin = MockingProxy.GetMockMixin(mock);
224            if (asMixin != null)
225                return asMixin;
226
227            return mock as IMockMixin;
228        }
229
230        private static IMockMixin GetMixinFromExternalDatabase(object obj, Type type)
231        {
232            bool isValueType = type.IsValueType;
233            lock (externalMixinDatabase)
234            {
235                if (isValueType)
236                {
237                    foreach (var entry in externalMixinDatabase)
238                    {
239                        object otherObj = entry.Key;
240                        if (AreTypesInRegistrySame(type, otherObj.GetType()))
241                        {
242                            bool equal = false;
243                            try
244                            {
245                                equal = AreValueTypeObjectsInRegistryEqual(obj, otherObj);
246                            }
247                            catch
248                            {
249                                throw new MockException("Implementation of method Equals in value types must not throw for mocked instances.");
250                            }
251
252                            if (equal)
253                                return entry.Value;
254                        }
255                    }
256                }
257                else
258                {
259                    IMockMixin result;
260                    externalReferenceMixinDatabase.TryGetValue(obj, out result);
261                    return result;
262                }
263            }
264
265            return null;
266        }
267
268        private static bool AreTypesInRegistrySame(Type queryType, Type typeInRegistry)
269        {
270            if (queryType == typeInRegistry)
271                return true;
272
273            return false;
274        }
275
276        private static bool AreValueTypeObjectsInRegistryEqual(object queryObj, object objInRegistry)
277        {
278            return Object.Equals(queryObj, objInRegistry); // no guard - Object.Equals on a value type can't get intercepted
279        }
280
281        private MethodInfoMatcherTreeNode DeepCopy(MethodInfoMatcherTreeNode root)
282        {
283            var newRoot = (MethodInfoMatcherTreeNode)root.Clone();
284            Queue<MatcherNodeAndParent> queue = new Queue<MatcherNodeAndParent>();
285            foreach (var child in root.Children)
286                queue.Enqueue(new MatcherNodeAndParent(child, newRoot));
287
288            while (queue.Count > 0)
289            {
290                MatcherNodeAndParent current = queue.Dequeue();
291                IMatcherTreeNode newCurrent = current.Node.Clone();
292                foreach (IMatcherTreeNode node in current.Node.Children)
293                {
294                    queue.Enqueue(new MatcherNodeAndParent(node, newCurrent));
295                }
296
297                current.Parent.Children.Add(newCurrent);
298                newCurrent.Parent = current.Parent;
299            }
300
301            return newRoot;
302        }
303
304        private void CopyConfigurationFromParent()
305        {
306            this.arrangementTreeRoots.Clear();
307            this.invocationTreeRoots.Clear();
308            this.valueStore.Clear();
309            this.controlledMocks.Clear();
310
311            if (parentRepository != null)
312            {
313                foreach (var root in parentRepository.arrangementTreeRoots)
314                {
315                    this.arrangementTreeRoots.Add(root.Key, DeepCopy(root.Value));
316                }
317
318                foreach (var root in parentRepository.invocationTreeRoots)
319                {
320                    this.invocationTreeRoots.Add(root.Key, DeepCopy(root.Value));
321                }
322
323                foreach (var kvp in parentRepository.valueStore)
324                {
325                    this.valueStore.Add(kvp.Key, kvp.Value);
326                }
327
328                foreach (WeakReference mockRef in parentRepository.controlledMocks)
329                {
330                    IMockMixin mixin = GetMockMixinFromAnyMock(mockRef.Target);
331                    if (mixin != null)
332                    {
333                        mixin.Repository = this;
334                        this.controlledMocks.Add(mockRef);
335                    }
336                }
337            }
338        }
339
340        internal void Retire()
341        {
342            this.IsRetired = true;
343            this.Reset();
344        }
345
346        internal void InterceptGlobally(MethodBase method)
347        {
348            if (this.globallyInterceptedMethods.Add(method))
349            {
350                ProfilerInterceptor.RegisterGlobalInterceptor(method, this);
351            }
352        }
353
354        internal void Reset()
355        {
356            DebugView.TraceEvent(IndentLevel.Configuration, () => String.Format("Resetting mock repository related to {0}.", this.Method));
357
358            foreach (var type in this.arrangedTypes)
359            {
360                ProfilerInterceptor.EnableInterception(type, false, this);
361            }
362
363            this.arrangedTypes.Clear();
364            this.staticMixinDatabase.Clear();
365
366            foreach (var method in this.globallyInterceptedMethods)
367            {
368                ProfilerInterceptor.UnregisterGlobalInterceptor(method, this);
369            }
370            this.globallyInterceptedMethods.Clear();
371
372            lock (externalMixinDatabase)
373            {
374                foreach (WeakReference mockRef in this.controlledMocks)
375                {
376                    IMockMixin mock = GetMockMixinFromAnyMock(mockRef.Target);
377                    if (mock != null && mock.ExternalizedMock != null && mock.Originator == this)
378                    {
379                        externalMixinDatabase.RemoveAll(kvp => kvp.Value == mock);
380                        externalReferenceMixinDatabase.Remove(mock.ExternalizedMock);
381                    }
382                }
383            }
384
385            this.controlledMocks.Clear();
386
387            this.CopyConfigurationFromParent();
388        }
389
390        internal void DispatchInvocation(Invocation invocation)
391        {
392            DebugView.TraceEvent(IndentLevel.DispatchResult, () => String.Format("Handling dispatch in repo #{0} servicing {1}", this.repositoryId, this.Method));
393
394            if (this.disabledTypes.Contains(invocation.Method.DeclaringType))
395            {
396                invocation.CallOriginal = true;
397                return;
398            }
399
400            invocation.InArrange = this.sharedContext.InArrange;
401            invocation.InArrangeArgMatching = this.sharedContext.InArrangeArgMatching;
402            invocation.InAssertSet = this.sharedContext.InAssertSet;
403            invocation.InRunClassConstructor = this.sharedContext.RunClassConstructorCount > 0;
404            invocation.Recording = this.Recorder != null;
405            invocation.RetainBehaviorDuringRecording = this.sharedContext.DispatchToMethodMocks;
406            invocation.Repository = this;
407
408            bool methodMockProcessed = false;
409            if (invocation.Recording)
410            {
411                this.Recorder.Record(invocation);
412            }
413            if (!invocation.Recording || invocation.RetainBehaviorDuringRecording)
414            {
415                methodMockProcessed = DispatchInvocationToMethodMocks(invocation);
416            }
417
418            invocation.ThrowExceptionIfNecessary();
419
420            if (!methodMockProcessed)
421            {
422                // We have to be careful for the potential exception throwing in the assertion context,
423                // so skip CallOriginalBehavior processing
424                var mock = invocation.MockMixin;
425                if (mock != null)
426                {
427                    var fallbackBehaviorsToExecute =
428                        mock.FallbackBehaviors
429                            .Where(behavior => !invocation.InAssertSet || !(behavior is CallOriginalBehavior))
430                            .ToList();
431                    foreach (var fallbackBehavior in fallbackBehaviorsToExecute)
432                    {
433                        fallbackBehavior.Process(invocation);
434                    }
435                }
436                else
437                {
438                    invocation.CallOriginal = CallOriginalBehavior.ShouldCallOriginal(invocation) && !invocation.InAssertSet;
439                }
440            }
441
442            if (!invocation.CallOriginal && !invocation.IsReturnValueSet && invocation.Method.GetReturnType() != typeof(void))
443            {
444                Type returnType = invocation.Method.GetReturnType();
445
446                object defaultValue = null;
447#if !PORTABLE
448                if (returnType.BaseType != null && returnType.BaseType == typeof(Task))
449                {
450                    Type taskGenericArgument = returnType.GenericTypeArguments.FirstOrDefault();
451                    object taskArgumentDefaultValue = taskGenericArgument.GetDefaultValue();
452
453                    // create a task with default value to return, by using the casting help method in MockingUtil
454                    MethodInfo castMethod = typeof(MockingUtil).GetMethod("TaskFromObject", BindingFlags.Static | BindingFlags.Public);
455                    MethodInfo castMethodGeneric = castMethod.MakeGenericMethod(taskGenericArgument);
456
457                    defaultValue = castMethodGeneric.Invoke(null, new object[] { taskArgumentDefaultValue });
458                }
459                else
460#endif
461                {
462                    defaultValue = returnType.GetDefaultValue();
463                }
464
465                invocation.ReturnValue = defaultValue;
466            }
467
468#if !PORTABLE
469            try
470            {
471                if (MockingContext.Plugins.Exists<IDebugWindowPlugin>())
472                {
473                    Func<object, Type, ObjectInfo> CreateObjectInfo = (value, type) =>
474                    {
475                        ObjectInfo resultInfo = (value != null) ? ObjectInfo.FromObject(value) : ObjectInfo.FromNullObject(type);
476
477                        return resultInfo;
478                    };
479
480                    var debugWindowPlugin = MockingContext.Plugins.Get<IDebugWindowPlugin>();
481                    var mockInfo = MockInfo.FromMethodBase(invocation.Method);
482
483                    ObjectInfo[] invocationsArgInfos =
484                        invocation.Args.Select(
485                                (arg, index) =>
486                                    {
487                                        var argInfo = CreateObjectInfo(arg, invocation.Method.GetParameters()[index].ParameterType);
488                                        return argInfo;
489                                    }).ToArray();
490
491                    ObjectInfo invocationInstanceInfo = ObjectInfo.FromObject(invocation.Instance ?? invocation.Method.DeclaringType);
492                    ObjectInfo invocationResultInfo = CreateObjectInfo(invocation.ReturnValue, invocation.Method.GetReturnType());
493
494                    var invocationInfo = new InvocationInfo(invocationInstanceInfo, invocationsArgInfos, invocationResultInfo);
495
496                    debugWindowPlugin.MockInvoked(
497                        invocation.Repository.RepositoryId,
498                        invocation.Repository.GetRepositoryPath(),
499                        mockInfo,
500                        invocationInfo);
501                }
502            }
503            catch (Exception e)
504            {
505                System.Diagnostics.Trace.WriteLine("Exception thrown calling IDebugWindowPlugin plugin: " + e);
506            }
507#endif
508        }
509
510        internal T GetValue<T>(object owner, object key, T dflt)
511        {
512            object value;
513            if (valueStore.TryGetValue(new KeyValuePair<object, object>(owner, key), out value))
514            {
515                return (T)value;
516            }
517            else
518            {
519                return dflt;
520            }
521        }
522
523        internal void StoreValue<T>(object owner, object key, T value)
524        {
525            valueStore[new KeyValuePair<object, object>(owner, key)] = value;
526        }
527
528        internal IDisposable StartRecording(IRecorder recorder, bool dispatchToMethodMocks)
529        {
530            return this.sharedContext.StartRecording(recorder, dispatchToMethodMocks);
531        }
532
533        internal IDisposable StartArrangeArgMatching()
534        {
535            return this.sharedContext.StartArrangeArgMatching();
536        }
537
538        internal void AddMatcherInContext(IMatcher matcher)
539        {
540            if (!this.sharedContext.InArrange || this.sharedContext.Recorder != null)
541            {
542                this.MatchersInContext.Add(matcher);
543            }
544        }
545
546        internal object Create(Type type, MockCreationSettings settings)
547        {
548            object delegateResult;
549            if (TryCreateDelegate(type, settings, out delegateResult))
550            {
551                return delegateResult;
552            }
553
554            bool isSafeMock = settings.FallbackBehaviors.OfType<CallOriginalBehavior>().Any();
555            this.CheckIfCanMock(type, !isSafeMock);
556
557            this.EnableInterception(type);
558
559            bool canCreateProxy = !type.IsSealed;
560
561            MockMixin mockMixinImpl = this.CreateMockMixin(type, settings);
562
563            ConstructorInfo[] ctors = type.GetConstructors();
564            bool isCoclass = ctors.Any(ctor => ctor.IsExtern());
565
566            bool hasAdditionalInterfaces = settings.AdditionalMockedInterfaces != null && settings.AdditionalMockedInterfaces.Length > 0;
567            bool hasAdditionalProxyTypeAttributes = settings.AdditionalProxyTypeAttributes != null && settings.AdditionalProxyTypeAttributes.Any();
568
569            bool shouldCreateProxy = settings.MustCreateProxy
570                || hasAdditionalInterfaces
571                || isCoclass
572                || type.IsAbstract || type.IsInterface
573                || !settings.MockConstructorCall
574                || hasAdditionalProxyTypeAttributes
575                || !ProfilerInterceptor.IsProfilerAttached
576                || !ProfilerInterceptor.TypeSupportsInstrumentation(type);
577
578            bool createTransparentProxy = MockingProxy.CanCreate(type) && !ProfilerInterceptor.IsProfilerAttached;
579
580            Exception proxyFailure = null;
581            object instance = null;
582            if (canCreateProxy && shouldCreateProxy)
583            {
584                try
585                {
586                    instance = mockFactory.Create(type, this, mockMixinImpl, settings, createTransparentProxy);
587                }
588                catch (ProxyFailureException ex)
589                {
590                    proxyFailure = ex.InnerException;
591                }
592            }
593
594            IMockMixin mockMixin = instance as IMockMixin;
595
596            if (instance == null)
597            {
598                if (type.IsInterface || type.IsAbstract)
599                    throw new MockException(String.Format("Abstract type '{0}' is not accessible for inheritance.", type));
600
601                if (hasAdditionalInterfaces)
602                    throw new MockException(String.Format("Type '{0}' is not accessible for inheritance. Cannot create mock object implementing the specified additional interfaces.", type));
603
604                if (!ProfilerInterceptor.IsProfilerAttached && !createTransparentProxy)
605                    ProfilerInterceptor.ThrowElevatedMockingException(type);
606
607                if (settings.MockConstructorCall && type.IsValueType)
608                {
609                    settings.MockConstructorCall = false;
610                    settings.Args = null;
611                }
612
613                if (!settings.MockConstructorCall)
614                {
615                    try
616                    {
617                        instance = type.CreateObject(settings.Args);
618                    }
619                    catch (MissingMethodException)
620                    {
621                        settings.MockConstructorCall = true;
622                    }
623                }
624
625                if (settings.MockConstructorCall)
626                {
627                    instance = MockingUtil.GetUninitializedObject(type);
628                }
629
630                if (!createTransparentProxy)
631                {
632                    mockMixin = this.CreateExternalMockMixin(type, instance, settings);
633                }
634            }
635            else
636            {
637                this.controlledMocks.Add(new WeakReference(instance));
638            }
639
640            if (type.IsClass)
641                GC.SuppressFinalize(instance);
642
643            if (createTransparentProxy)
644            {
645                if (mockMixin == null)
646                {
647                    mockMixin = mockMixinImpl;
648                }
649                instance = MockingProxy.CreateProxy(instance, this, mockMixin);
650            }
651
652            mockMixin.IsInstanceConstructorMocked = settings.MockConstructorCall;
653
654            return instance;
655        }
656
657        internal IMockMixin CreateExternalMockMixin(Type mockObjectType, object mockObject, MockCreationSettings settings)
658        {
659            if (mockObjectType == null)
660            {
661                if (mockObject == null)
662                    throw new ArgumentNullException("mockObject");
663                mockObjectType = mockObject.GetType();
664            }
665
666            this.EnableInterception(mockObjectType);
667
668            if (mockObject == null)
669                throw new MockException(String.Format("Failed to create instance of type '{0}'", mockObjectType));
670
671            MockMixin mockMixin = this.CreateMockMixin(mockObjectType, settings, false);
672            IMockMixin compoundMockMixin = mockFactory.CreateExternalMockMixin(mockMixin, settings.Mixins);
673            lock (externalMixinDatabase)
674            {
675                if (mockObjectType.IsValueType())
676                {
677                    externalMixinDatabase.RemoveAll(kvp => kvp.Key.Equals(mockObject));
678                    externalMixinDatabase.Add(new KeyValuePair<object, IMockMixin>(mockObject, compoundMockMixin));
679                }
680                else
681                {
682                    externalReferenceMixinDatabase.Add(mockObject, compoundMockMixin);
683                }
684
685                compoundMockMixin.ExternalizedMock = mockObject;
686                this.controlledMocks.Add(new WeakReference(compoundMockMixin));
687            }
688
689            return compoundMockMixin;
690        }
691
692        internal ProxyTypeInfo CreateClassProxyType(Type classToProxy, MockCreationSettings settings)
693        {
694            MockMixin mockMixinImpl = this.CreateMockMixin(classToProxy, settings);
695            return mockFactory.CreateClassProxyType(classToProxy, this, settings, mockMixinImpl);
696        }
697
698        private void CheckIfCanMock(Type type, bool checkSafety)
699        {
700            if (KnownUnmockableTypes.Contains(type)
701                || typeof(Delegate).IsAssignableFrom(type))
702                throw new MockException("Cannot create mock for type due to CLR limitations.");
703
704            if (checkSafety)
705                ProfilerInterceptor.CheckIfSafeToInterceptWholesale(type);
706        }
707
708        internal void InterceptStatics(Type type, MockCreationSettings settings, bool mockStaticConstructor)
709        {
710            if (!ProfilerInterceptor.IsProfilerAttached)
711                ProfilerInterceptor.ThrowElevatedMockingException(type);
712
713            if (!settings.FallbackBehaviors.OfType<CallOriginalBehavior>().Any())
714                ProfilerInterceptor.CheckIfSafeToInterceptWholesale(type);
715
716            var mockMixin = (IMockMixin)Create(typeof(ExternalMockMixin),
717                new MockCreationSettings
718                {
719                    Mixins = settings.Mixins,
720                    SupplementaryBehaviors = settings.SupplementaryBehaviors,
721                    FallbackBehaviors = settings.FallbackBehaviors,
722                    MustCreateProxy = true,
723                });
724
725            mockMixin.IsStaticConstructorMocked = mockStaticConstructor;
726            lock (staticMixinDatabase)
727                staticMixinDatabase[type] = mockMixin;
728
729            this.EnableInterception(type);
730        }
731
732        private MockMixin CreateMockMixin(Type declaringType, MockCreationSettings settings)
733        {
734            return CreateMockMixin(declaringType, settings, settings.MockConstructorCall);
735        }
736
737        private MockMixin CreateMockMixin(Type declaringType, MockCreationSettings settings, bool mockConstructorCall)
738        {
739            var mockMixin = new MockMixin
740            {
741                Repository = this,
742                DeclaringType = declaringType,
743                IsInstanceConstructorMocked = mockConstructorCall,
744            };
745
746            foreach (var behavior in settings.SupplementaryBehaviors)
747                mockMixin.SupplementaryBehaviors.Add(behavior);
748
749            foreach (var behavior in settings.FallbackBehaviors)
750                mockMixin.FallbackBehaviors.Add(behavior);
751
752            return mockMixin;
753        }
754
755        [ArrangeMethod]
756        internal TMethodMock Arrange<TMethodMock>(Expression expression, Func<TMethodMock> methodMockFactory)
757            where TMethodMock : IMethodMock
758        {
759            TMethodMock result = methodMockFactory();
760
761            using (this.sharedContext.StartArrange())
762            {
763                result.Repository = this;
764                result.ArrangementExpression = ExpressionUtil.ConvertMockExpressionToString(expression);
765                result.CallPattern = CallPatternCreator.FromExpression(this, expression);
766
767                AddArrange(result);
768            }
769
770#if !PORTABLE
771            var createInstanceLambda = ActivatorCreateInstanceTBehavior.TryCreateArrangementExpression(result.CallPattern.Method);
772            if (createInstanceLambda != null)
773            {
774                var createInstanceMethodMock = Arrange(createInstanceLambda, methodMockFactory);
775                ActivatorCreateInstanceTBehavior.Attach(result, createInstanceMethodMock);
776            }
777#endif
778
779            return result;
780        }
781
782        /// <summary>
783        /// Since we are not able to record deffered execution, check whether mock is about to present
784        /// IQueryable<> or IEnumerable<>. If it so, then throw an exception to prompt the user to use
785        /// Mock.Arrange overloads with expressions.
786        /// See also https://stackoverflow.com/questions/3894490/linq-what-is-the-quickest-way-to-find-out-deferred-execution-or-not
787        /// </summary>
788        /// <typeparam name="TMethodMock">Mock return type</typeparam>
789        /// <param name="methodMockFactory">Mock factory</param>
790        private void CheckDefferedExecutionTypes<TMethodMock>(Func<TMethodMock> methodMockFactory)
791        {
792            var mockFactoryType = methodMockFactory.GetType().GetGenericArguments().FirstOrDefault();
793            if (mockFactoryType.IsGenericType && mockFactoryType.GetGenericTypeDefinition() == typeof(FuncExpectation<>))
794            {
795                var mockFactoryGenericArguments = mockFactoryType.GetGenericArguments();
796                if (mockFactoryGenericArguments
797                        .Where(a => a.IsGenericType)
798                        .Where(a => a.GetGenericTypeDefinition() == typeof(IQueryable<>)
799                            || a.GetGenericTypeDefinition() == typeof(IEnumerable<>))
800                        .Any())
801                {
802                    throw new MockException("Cannot arrange action with potential deffered execution, use Mock.Arrange with expressions instead.");
803                }
804            }
805        }
806
807        [ArrangeMethod]
808        internal TMethodMock Arrange<TMethodMock>(Action memberAction, Func<TMethodMock> methodMockFactory)
809            where TMethodMock : IMethodMock
810        {
811            using (this.sharedContext.StartArrange())
812            {
813                var result = methodMockFactory();
814
815                CheckDefferedExecutionTypes(methodMockFactory);
816
817                result.Repository = this;
818                result.CallPattern = CallPatternCreator.FromAction(this, memberAction);
819
820                AddArrange(result);
821                return result;
822            }
823        }
824
825        [ArrangeMethod]
826        internal TMethodMock Arrange<TMethodMock>(object instance, MethodBase method, object[] arguments, Func<TMethodMock> methodMockFactory)
827            where TMethodMock : IMethodMock
828        {
829            using (this.sharedContext.StartArrange())
830            {
831                var result = methodMockFactory();
832                result.Repository = this;
833                result.CallPattern = CallPatternCreator.FromMethodBase(this, instance, method, arguments);
834
835                AddArrange(result);
836                return result;
837            }
838        }
839
840        [ArrangeMethod]
841        internal TMethodMock Arrange<TMethodMock>(CallPattern callPattern, Func<TMethodMock> methodMockFactory)
842            where TMethodMock : IMethodMock
843        {
844            using (this.sharedContext.StartArrange())
845            {
846                var result = methodMockFactory();
847                result.Repository = this;
848                result.CallPattern = callPattern;
849
850                AddArrange(result);
851                return result;
852            }
853        }
854
855        private void AssertBehaviorsForMocks(IEnumerable<IMethodMock> mocks, bool ignoreMethodMockOccurrences)
856        {
857            foreach (var methodMock in mocks)
858            {
859                bool occurrenceAsserted = ignoreMethodMockOccurrences;
860                if (!ignoreMethodMockOccurrences
861                    && !methodMock.OccurencesBehavior.LowerBound.HasValue
862                    && !methodMock.OccurencesBehavior.UpperBound.HasValue)
863                {
864                    methodMock.OccurencesBehavior.Assert(1, null);
865                    occurrenceAsserted = true;
866                }
867
868                foreach (var behavior in methodMock.Behaviors.OfType<IAssertableBehavior>())
869                    if (!occurrenceAsserted || behavior != methodMock.OccurencesBehavior)
870                        behavior.Assert();
871            }
872        }
873
874        internal void AssertAll(string message, object mock)
875        {
876            using (MockingContext.BeginFailureAggregation(message))
877            {
878                var mocks = GetMethodMocksFromObject(mock);
879                AssertBehaviorsForMocks(mocks.Select(m => m.MethodMock), false);
880            }
881        }
882
883        internal void AssertAll(string message)
884        {
885            using (MockingContext.BeginFailureAggregation(message))
886            {
887                var mocks = GetAllMethodMocks();
888                AssertBehaviorsForMocks(mocks.Select(m => m.MethodMock), false);
889            }
890        }
891
892        internal void Assert(string message, object mock, Expression expr = null, Args args = null, Occurs occurs = null)
893        {
894            using (MockingContext.BeginFailureAggregation(message))
895            {
896                if (expr == null)
897                {
898                    List<IMethodMock> mocks = new List<IMethodMock>();
899                    mocks.AddRange(GetMethodMocksFromObject(mock).Select(m => m.MethodMock));
900                    foreach (var methodMock in mocks)
901                    {
902                        foreach (var behavior in methodMock.Behaviors.OfType<IAssertableBehavior>())
903                            behavior.Assert();
904                    }
905
906                    MockingUtil.UnwrapDelegateTarget(ref mock);
907                    var mockMixin = GetMockMixin(mock, null);
908                    if (mockMixin != null)
909                    {
910                        foreach (var behavior in mockMixin.FallbackBehaviors.OfType<IAssertableBehavior>()
911                            .Concat(mockMixin.SupplementaryBehaviors.OfType<IAssertableBehavior>()))
912                            behavior.Assert();
913                    }
914                }
915                else
916                {
917                    CallPattern callPattern = CallPatternCreator.FromExpression(this, expr);
918                    AssertForCallPattern(callPattern, args, occurs);
919                }
920            }
921        }
922
923        internal void AssertAction(string message, Action memberAction, Args args = null, Occurs occurs = null)
924        {
925            using (MockingContext.BeginFailureAggregation(message))
926            {
927                CallPattern callPattern = CallPatternCreator.FromAction(this, memberAction);
928                AssertForCallPattern(callPattern, args, occurs);
929            }
930        }
931
932        internal void AssertSetAction(string message, Action memberAction, Args args = null, Occurs occurs = null)
933        {
934            using (MockingContext.BeginFailureAggregation(message))
935            {
936                using (this.sharedContext.StartAssertSet())
937                {
938                    CallPattern callPattern = CallPatternCreator.FromAction(this, memberAction, true);
939                    AssertForCallPattern(callPattern, args, occurs);
940                }
941            }
942        }
943
944        internal void AssertMethodInfo(string message, object instance, MethodBase method, object[] arguments, Occurs occurs)
945        {
946            using (MockingContext.BeginFailureAggregation(message))
947            {
948                CallPattern callPattern = CallPatternCreator.FromMethodBase(instance, method, arguments);
949                AssertForCallPattern(callPattern, null, occurs);
950            }
951        }
952
953        internal void AssertIgnoreInstance(string message, Type type, bool ignoreMethodMockOccurrences)
954        {
955            using (MockingContext.BeginFailureAggregation(message))
956            {
957                var methodMocks = GetMethodMocksFromObject(null, type);
958                AssertBehaviorsForMocks(methodMocks.Select(m => m.MethodMock), ignoreMethodMockOccurrences);
959            }
960        }
961
962        internal int GetTimesCalled(Expression expression, Args args)
963        {
964            CallPattern callPattern = CallPatternCreator.FromExpression(this, expression);
965
966            int callsCount;
967            CountMethodMockInvocations(callPattern, args, out callsCount);
968            return callsCount;
969        }
970
971        internal int GetTimesCalledFromAction(Action action, Args args)
972        {
973            var callPattern = CallPatternCreator.FromAction(this, action);
974            int callsCount;
975            CountMethodMockInvocations(callPattern, args, out callsCount);
976            return callsCount;
977        }
978
979        internal int GetTimesCalledFromMethodInfo(object instance, MethodBase method, object[] arguments)
980        {
981            var callPattern = CallPatternCreator.FromMethodBase(instance, method, arguments);
982            int callsCount;
983            CountMethodMockInvocations(callPattern, null, out callsCount);
984            return callsCount;
985        }
986
987        private HashSet<IMethodMock> CountMethodMockInvocations(CallPattern callPattern, Args args, out int callsCount)
988        {
989            if (callPattern.IsDerivedFromObjectEquals)
990                throw new MockException("Cannot assert calls to methods derived from Object.Equals");
991
992            PreserveRefOutValuesBehavior.ReplaceRefOutArgsWithAnyMatcher(callPattern);
993
994            if (args != null)
995            {
996                if (args.Filter != null)
997                {
998                    if (args.IsIgnored == null)
999                    {
1000                        args.IsIgnored = true;
1001                    }
1002                    if (!callPattern.Method.IsStatic
1003                        && args.IsInstanceIgnored == null
1004                        && args.Filter.Method.GetParameters().Length == callPattern.Method.GetParameters().Length + 1)
1005                    {
1006                        args.IsInstanceIgnored = true;
1007                    }
1008                }
1009
1010                if (args.IsIgnored == true)
1011                {
1012                    for (int i = 0; i < callPattern.ArgumentMatchers.Count; i++)
1013                        callPattern.ArgumentMatchers[i] = new AnyMatcher();
1014                }
1015
1016                if (args.IsInstanceIgnored == true)
1017                {
1018                    callPattern.InstanceMatcher = new AnyMatcher();
1019                }
1020
1021                callPattern.Filter = args.Filter;
1022            }
1023
1024            MethodInfoMatcherTreeNode root;
1025            callsCount = 0;
1026            var mocks = new HashSet<IMethodMock>();
1027            var method = callPattern.Method;
1028            if (invocationTreeRoots.TryGetValue(method, out root))
1029            {
1030                var occurences = root.GetOccurences(callPattern);
1031                callsCount = occurences.Select(x => x.Calls).Sum();
1032                foreach (var mock in occurences.SelectMany(x => x.Mocks))
1033                {
1034                    mocks.Add(mock);
1035                }
1036            }
1037            return mocks;
1038        }
1039
1040        private void AssertForCallPattern(CallPattern callPattern, Args args, Occurs occurs)
1041        {
1042            int callsCount;
1043            var mocks = CountMethodMockInvocations(callPattern, args, out callsCount);
1044            if (occurs != null)
1045            {
1046                InvocationOccurrenceBehavior.Assert(occurs.LowerBound, occurs.UpperBound, callsCount, null, null);
1047            }
1048
1049            if (mocks.Count == 0)
1050            {
1051                MethodInfoMatcherTreeNode funcRoot;
1052                if (arrangementTreeRoots.TryGetValue(callPattern.Method, out funcRoot))
1053                {
1054                    var arranges = funcRoot.GetAllMethodMocks(callPattern);
1055                    foreach (var arrange in arranges)
1056                    {
1057                        mocks.Add(arrange.MethodMock);
1058                    }
1059                }
1060            }
1061            if (occurs == null && mocks.Count == 0)
1062            {
1063                InvocationOccurrenceBehavior.Assert(Occurs.AtLeastOnce().LowerBound, Occurs.AtLeastOnce().UpperBound, callsCount, null, null);
1064            }
1065            else
1066            {
1067                AssertBehaviorsForMocks(mocks, occurs != null);
1068            }
1069        }
1070
1071        internal MethodBase GetMethodFromCallPattern(CallPattern callPattern)
1072        {
1073            var method = callPattern.Method as MethodInfo;
1074            if (method == null || !method.IsVirtual)
1075                return callPattern.Method;
1076
1077            method = method.NormalizeComInterfaceMethod();
1078            var valueMatcher = callPattern.InstanceMatcher as IValueMatcher;
1079            if (valueMatcher != null && valueMatcher.Value != null)
1080            {
1081                var valueType = valueMatcher.Value.GetType();
1082                var mockMixin = GetMockMixin(valueMatcher.Value, null);
1083
1084                var type = mockMixin != null ? mockMixin.DeclaringType : valueType;
1085                if (!type.IsInterface && method.DeclaringType.IsAssignableFrom(type))
1086                {
1087                    var concreteMethod = MockingUtil.GetConcreteImplementer(method, type);
1088                    if (!concreteMethod.IsInheritable() && !ProfilerInterceptor.IsProfilerAttached)
1089                    {
1090                        var reimplementedInterfaceMethod = (MethodInfo)method.GetInheritanceChain().Last();
1091                        if (reimplementedInterfaceMethod.DeclaringType.IsInterface
1092                            && mockFactory.IsAccessible(reimplementedInterfaceMethod.DeclaringType))
1093                        {
1094                            concreteMethod = reimplementedInterfaceMethod;
1095                        }
1096                    }
1097                    method = concreteMethod;
1098                }
1099            }
1100
1101            if (method.DeclaringType != method.ReflectedType)
1102                method = (MethodInfo)MethodBase.GetMethodFromHandle(method.MethodHandle, method.DeclaringType.TypeHandle);
1103
1104            return method;
1105        }
1106
1107        /// <summary>
1108        /// Converts the given object to a matcher as follows. This method is most useful for
1109        /// creating a matcher out of an argument expression.
1110        /// 
1111        /// It works as follows:
1112        /// If the object is not an expression, then a value matcher for that object is returned.
1113        /// If the object is an expression then:
1114        /// * if the top of the expression is a method call expression and the member
1115        ///     has the ArgMatcherAttribute then the specific matcher type is instantiaded
1116        ///     with the parameters passed to the method call expression and returned.
1117        ///     If the matcher type is generic, then it is defined with the type of the expression.
1118        /// * if the top expression is a member or method call and the member
1119        ///     has the ArgIgnoreAttribute, then a TypeMatcher is returned
1120        /// * otherwise, the expression is evaluated and a ValueMatcher is returned
1121        /// </summary>
1122        /// <param name="argumentObj"></param>
1123        /// <returns></returns>
1124        internal static IMatcher CreateMatcherForArgument(object argumentObj)
1125        {
1126            var argExpr = argumentObj as Expression;
1127            if (argExpr == null)
1128            {
1129                return new ValueMatcher(argumentObj);
1130            }
1131            else
1132            {
1133                var argMatcher = TryCreateMatcherFromArgMember(argExpr);
1134                if (argMatcher != null)
1135                {
1136                    return argMatcher;
1137                }
1138                else
1139                {
1140                    //no matcher, evaluate the original expression
1141                    var argValue = argExpr.EvaluateExpression();
1142                    return new ValueMatcher(argValue);
1143                }
1144            }
1145        }
1146
1147        internal static IMatcher TryCreateMatcherFromArgMember(Expression argExpr)
1148        {
1149            // The expression may end with a conversion which erases the type of the required matcher.
1150            // Remove the conversion before working with matchers
1151            while (argExpr.NodeType == ExpressionType.Convert)
1152                argExpr = ((UnaryExpression)argExpr).Operand;
1153
1154            ArgMatcherAttribute argAttribute = null;
1155            Expression[] matcherArguments = null;
1156            if (argExpr is MethodCallExpression)
1157            {
1158                var methodCall = (MethodCallExpression)argExpr;
1159                argAttribute = (ArgMatcherAttribute)Attribute.GetCustomAttribute(methodCall.Method, typeof(ArgMatcherAttribute));
1160                matcherArguments = methodCall.Arguments.ToArray();
1161            }
1162            else if (argExpr is MemberExpression)
1163            {
1164                var memberExpr = (MemberExpression)argExpr;
1165                argAttribute = (ArgMatcherAttribute)Attribute.GetCustomAttribute(memberExpr.Member, typeof(ArgMatcherAttribute));
1166            }
1167
1168            if (argAttribute != null)
1169            {
1170                if (argAttribute.GetType() == typeof(ArgIgnoreAttribute))
1171                {
1172                    return new TypeMatcher(argExpr.Type);
1173                }
1174                else if (argAttribute.GetType() == typeof(ArgIgnoreTypeAttribute))
1175                {
1176                    var func = Expression.Lambda(argExpr).Compile();
1177                    var funcResult = func.DynamicInvoke();
1178                    return new TypeMatcher(funcResult.GetType());
1179                }
1180                else if (argAttribute.GetType() == typeof(RefArgAttribute) || argAttribute.GetType() == typeof(OutArgAttribute))
1181                {
1182                    var asMemberExpr = argExpr as MemberExpression;
1183                    if (asMemberExpr != null)
1184                    {
1185                        argExpr = asMemberExpr.Expression;
1186                    }
1187                    var refCall = (MethodCallExpression)argExpr;
1188                    var actualArg = refCall.Arguments[0];
1189                    var memberExpr = actualArg as MemberExpression;
1190                    if (memberExpr != null && typeof(Expression).IsAssignableFrom(memberExpr.Type) && memberExpr.Expression.Type.DeclaringType == typeof(ArgExpr))
1191                    {
1192                        actualArg = (Expression)actualArg.EvaluateExpression();
1193                    }
1194                    var argMatcher = CreateMatcherForArgument(actualArg);
1195                    argMatcher.ProtectRefOut = argAttribute.GetType() == typeof(RefArgAttribute);
1196                    return argMatcher;
1197                }
1198                else
1199                {
1200                    var matcherType = argAttribute.Matcher;
1201                    var matcherArgs = argAttribute.MatcherArgs;
1202
1203                    if (matcherType.IsGenericTypeDefinition)
1204                        matcherType = matcherType.MakeGenericType(argExpr.Type);
1205
1206                    if (matcherArgs == null && matcherArguments != null)
1207                        matcherArgs = matcherArguments.Select(matcherArgExpr => matcherArgExpr.EvaluateExpression()).ToArray();
1208
1209                    var matcher = (IMatcher)MockingUtil.CreateInstance(matcherType, matcherArgs);
1210                    return matcher;
1211                }
1212            }
1213            else
1214            {
1215                return null;
1216            }
1217        }
1218
1219        private void AddArrange(IMethodMock methodMock)
1220        {
1221            var method = methodMock.CallPattern.Method;
1222
1223            if (methodMock.CallPattern.IsDerivedFromObjectEquals && method.ReflectedType.IsValueType())
1224                throw new MockException("Cannot mock Equals method because JustMock depends on it. Also, when Equals is called internally by JustMock, all methods called by it will not be intercepted and will have only their original implementations called.");
1225
1226            CheckMethodInterceptorAvailable(methodMock.CallPattern.InstanceMatcher, method);
1227
1228            Type declaringType = method.DeclaringType;
1229
1230            // If we're arranging a call to a mock object, overwrite its repository.
1231            // This will ensure correct behavior when a mock object is arranged
1232            // in multiple contexts.
1233            var refMatcher = methodMock.CallPattern.InstanceMatcher as ReferenceMatcher;
1234            if (refMatcher != null)
1235            {
1236                var value = refMatcher.Value;
1237                var mock = GetMockMixin(value, declaringType);
1238                if (mock != null)
1239                {
1240                    methodMock.Mock = mock;
1241                    mock.Repository = this;
1242                }
1243
1244                if (value != null)
1245                    EnableInterception(value.GetType());
1246            }
1247
1248            EnableInterception(declaringType);
1249
1250            PreserveRefOutValuesBehavior.Attach(methodMock);
1251            ConstructorMockBehavior.Attach(methodMock);
1252
1253            MethodInfoMatcherTreeNode funcRoot;
1254            if (!arrangementTreeRoots.TryGetValue(method, out funcRoot))
1255            {
1256                funcRoot = new MethodInfoMatcherTreeNode(method);
1257                arrangementTreeRoots.Add(method, funcRoot);
1258            }
1259
1260            funcRoot.AddChild(methodMock.CallPattern, methodMock, this.sharedContext.GetNextArrangeId());
1261
1262#if !PORTABLE
1263            if (ProfilerInterceptor.IsReJitEnabled)
1264            {
1265                CallPattern.CheckMethodCompatibility(method);
1266                CallPattern.CheckInstrumentationAvailability(method);
1267
1268                ProfilerInterceptor.RequestReJit(method);
1269            }
1270
1271            try
1272            {
1273                if (MockingContext.Plugins.Exists<IDebugWindowPlugin>())
1274                {
1275                    var debugWindowPlugin = MockingContext.Plugins.Get<IDebugWindowPlugin>();
1276                    var argumentMatchers =
1277                        methodMock.CallPattern.ArgumentMatchers
1278                            .SelectMany(
1279                                (IMatcher matcher, int index) =>
1280                                    {
1281                                        MatcherInfo[] paramsMatchers;
1282                                        var matcherInfo = MatcherInfo.FromMatcherAndParamInfo(matcher, methodMock.CallPattern.Method.GetParameters()[index], out paramsMatchers);
1283                                        if (matcherInfo.Kind == MatcherInfo.MatcherKind.Params && paramsMatchers.Length > 0)
1284                                        {
1285                                            // skip params matcher, but add all contained matchers
1286                                            return paramsMatchers;
1287                                        }
1288                                        return new MatcherInfo[] { matcherInfo };
1289                                    },
1290                                (IMatcher matcher, MatcherInfo resultMatcherInfo) => resultMatcherInfo)
1291                            .ToArray();
1292
1293                    debugWindowPlugin.MockCreated(
1294                        methodMock.Repository.RepositoryId,
1295                        methodMock.Repository.GetRepositoryPath(),
1296                        MockInfo.FromMethodBase(methodMock.CallPattern.Method),
1297                        argumentMatchers);
1298                }
1299            }
1300            catch (Exception e)
1301            {
1302                System.Diagnostics.Trace.WriteLine("Exception thrown calling IDebugWindowPlugin plugin: " + e);
1303            }
1304#endif
1305        }
1306
1307#if !PORTABLE
1308        internal void UpdateMockDebugView(MethodBase method, IMatcher[] argumentMatchers)
1309        {
1310            try
1311            {
1312                if (MockingContext.Plugins.Exists<IDebugWindowPlugin>())
1313                {
1314                    var debugWindowPlugin = MockingContext.Plugins.Get<IDebugWindowPlugin>();
1315
1316                    MatcherInfo[] argumentMatcherInfos = null;
1317                    if (argumentMatchers != null)
1318                    {
1319                        argumentMatcherInfos =
1320                            argumentMatchers.Select(
1321                                (IMatcher matcher, int index) =>
1322                                    {
1323                                        MatcherInfo[] dummy;
1324                                        return MatcherInfo.FromMatcherAndParamInfo(matcher, method.GetParameters()[index], out dummy);
1325                                    })
1326                            .ToArray();
1327                    }
1328
1329                    debugWindowPlugin.MockUpdated(
1330                        this.RepositoryId,
1331                        this.GetRepositoryPath(),
1332                        MockInfo.FromMethodBase(method),
1333                        argumentMatcherInfos);
1334                }
1335            }
1336            catch (Exception e)
1337            {
1338                System.Diagnostics.Trace.WriteLine("Exception thrown calling IDebugWindowPlugin plugin: " + e);
1339            }
1340        }
1341#endif
1342
1343        private void CheckMethodInterceptorAvailable(IMatcher instanceMatcher, MethodBase method)
1344        {
1345            if (ProfilerInterceptor.IsProfilerAttached)
1346                return;
1347
1348            var valueMatcher = instanceMatcher as IValueMatcher;
1349            if (valueMatcher == null)
1350                return;
1351
1352            var instance = valueMatcher.Value;
1353            if (instance == null)
1354                return;
1355            if (MockingProxy.CanIntercept(instance, method))
1356                return;
1357
1358            if (!(instance is IMockMixin) || !method.IsInheritable())
1359                ProfilerInterceptor.ThrowElevatedMockingException(method);
1360        }
1361
1362#if !PORTABLE
1363        private void RequestRejitForTypeMethods(Type typeToIntercept)
1364        {
1365            var methods = new HashSet<MethodBase>();
1366            methods.UnionWith(typeToIntercept.GetMethods(MockingUtil.AllMembers));
1367            methods.UnionWith(
1368                typeToIntercept.GetInheritanceChain()
1369                    .Where(type => type != typeToIntercept)
1370                    .SelectMany(type => type.GetMethods(MockingUtil.AllMembers | BindingFlags.DeclaredOnly))
1371                    .Where(method => !method.IsAbstract && method.DeclaringType != typeof(object)));
1372            methods.UnionWith(
1373                MockingUtil.GetAllProperties(typeToIntercept)
1374                    .SelectMany(
1375                        property =>
1376                            {
1377                                var propertyMethods = new List<MethodBase>();
1378                                var getMethod = property.GetMethod;
1379                                if (getMethod != null)
1380                                {
1381                                    propertyMethods.Add(getMethod);
1382                                }
1383                                var setMethod = property.SetMethod;
1384                                if (setMethod != null)
1385                                {
1386                                    propertyMethods.Add(setMethod);
1387                                }
1388                                return propertyMethods;
1389                            }));
1390            foreach (var method in methods)
1391            {
1392                try
1393                {
1394                    CallPattern.CheckMethodCompatibility(method);
1395                    CallPattern.CheckInstrumentationAvailability(method);
1396
1397                    ProfilerInterceptor.RequestReJit(method);
1398                }
1399                catch (MockException e)
1400                {
1401#if DEBUG
1402                    System.Diagnostics.Trace.WriteLine(string.Format("Method {0} is skipped for interception: {1}", method, e));
1403#endif
1404                }
1405            }
1406        }
1407#endif
1408
1409        internal void EnableInterception(Type typeToIntercept)
1410        {
1411            if (ProfilerInterceptor.IsProfilerAttached)
1412            {
1413                for (var type = typeToIntercept; type != null;)
1414                {
1415                    if (!ProfilerInterceptor.TypeSupportsInstrumentation(type))
1416                        DebugView.TraceEvent(IndentLevel.Warning, () => String.Format("Elevated mocking for type {0} will not be available due to limitations in CLR", type));
1417
1418                    if (this.arrangedTypes.Add(type))
1419                    {
1420                        ProfilerInterceptor.EnableInterception(type, true, this);
1421#if !PORTABLE
1422                        if (ProfilerInterceptor.IsReJitEnabled && type == typeToIntercept)
1423                        {
1424                            RequestRejitForTypeMethods(type);
1425                        }
1426#endif
1427                    }
1428
1429                    type = type.BaseType;
1430
1431                    if (type == typeof(object) || type == typeof(ValueType) || type == typeof(Enum))
1432                        break;
1433                }
1434
1435                using (this.sharedContext.StartRunClassConstructor())
1436                {
1437                    var handle = typeToIntercept.TypeHandle;
1438                    this.disabledTypes.Add(typeof(RuntimeHelpers));
1439                    ProfilerInterceptor.RunClassConstructor(handle);
1440                    this.disabledTypes.Remove(typeof(RuntimeHelpers));
1441                }
1442            }
1443        }
1444
1445        internal void DisableInterception(Type typeToIntercept)
1446        {
1447            if (ProfilerInterceptor.IsProfilerAttached)
1448            {
1449                if (this.arrangedTypes.Remove(typeToIntercept))
1450                {
1451                    ProfilerInterceptor.EnableInterception(typeToIntercept, false, this);
1452                }
1453
1454                this.disabledTypes.Add(typeToIntercept);
1455            }
1456        }
1457
1458        private List<MethodMockMatcherTreeNode> GetMethodMocksFromObject(object mock, Type mockType = null)
1459        {
1460            MockingUtil.UnwrapDelegateTarget(ref mock);
1461            var methodMocks = new List<MethodMockMatcherTreeNode>();
1462            var visitedMocks = new List<object>(); // can't be HashSet because we can't depend on GetHashCode being implemented properly
1463            GetMethodMocksFromObjectInternal(mock, mockType, methodMocks, visitedMocks);
1464            return methodMocks;
1465        }
1466
1467        private void GetMethodMocksFromObjectInternal(object mock, Type mockType, List<MethodMockMatcherTreeNode> methodMocks, List<object> visitedMocks)
1468        {
1469            if (visitedMocks.Contains(mock))
1470                return;
1471            visitedMocks.Add(mock);
1472
1473            IMatcher instanceMatcher;
1474            Func<MethodInfoMatcherTreeNode, bool> rootMatcher;
1475            if (mockType != null)
1476            {
1477                instanceMatcher = new AnyMatcher();
1478                rootMatcher = node => mockType.IsAssignableFrom(node.MethodInfo.DeclaringType);
1479            }
1480            else
1481            {
1482                instanceMatcher = new ValueMatcher(mock);
1483                rootMatcher = node =>
1484                {
1485                    foreach (var child in node.Children)
1486                    {
1487                        if (child.Matcher.Matches(instanceMatcher))
1488                        {
1489                            return true;
1490                        }
1491                    }
1492                    return false;
1493                };
1494            }
1495
1496            foreach (var funcRoot in arrangementTreeRoots.Values.Where(rootMatcher))
1497            {
1498                var callPattern = CallPattern.CreateUniversalCallPattern(funcRoot.MethodInfo);
1499                callPattern.InstanceMatcher = instanceMatcher;
1500
1501                var results = funcRoot.GetAllMethodMocks(callPattern);
1502                methodMocks.AddRange(results);
1503            }
1504
1505            if (mock != null)
1506            {
1507                var mockMixin = GetMockMixin(mock, null);
1508                if (mockMixin != null)
1509                {
1510                    foreach (var dependentMock in mockMixin.DependentMocks)
1511                        GetMethodMocksFromObjectInternal(dependentMock, null, methodMocks, visitedMocks);
1512                }
1513            }
1514        }
1515
1516        private List<MethodMockMatcherTreeNode> GetAllMethodMocks()
1517        {
1518            var methodMocks = new List<MethodMockMatcherTreeNode>();
1519            GetAllMethodMocksInternal(methodMocks);
1520            return methodMocks;
1521        }
1522
1523        private void GetAllMethodMocksInternal(List<MethodMockMatcherTreeNode> methodMocks)
1524        {
1525            foreach (var funcRoot in arrangementTreeRoots.Values)
1526            {
1527                var callPattern = CallPattern.CreateUniversalCallPattern(funcRoot.MethodInfo);
1528                var results = funcRoot.GetAllMethodMocks(callPattern);
1529                methodMocks.AddRange(results);
1530            }
1531        }
1532
1533        private bool DispatchInvocationToMethodMocks(Invocation invocation)
1534        {
1535            CallPattern callPattern = CallPatternCreator.FromInvocation(invocation);
1536
1537            MethodInfoMatcherTreeNode funcRoot = null;
1538            if (!invocation.InArrange && !invocation.InAssertSet)
1539            {
1540                if (!invocationTreeRoots.TryGetValue(callPattern.Method, out funcRoot))
1541                {
1542                    funcRoot = new MethodInfoMatcherTreeNode(callPattern.Method);
1543                    invocationTreeRoots.Add(callPattern.Method, funcRoot);
1544                }
1545            }
1546
1547            var methodMock = DispatchInvocationToArrangements(callPattern, invocation);
1548
1549            if (!invocation.InArrange && !invocation.InAssertSet)
1550            {
1551                funcRoot.AddOrUpdateOccurence(callPattern, methodMock);
1552            }
1553
1554            return methodMock != null;
1555        }
1556
1557        private IMethodMock DispatchInvocationToArrangements(CallPattern callPattern, Invocation invocation)
1558        {
1559            MethodInfoMatcherTreeNode arrangeFuncRoot;
1560            var methodMockNodes = new List<MethodMockMatcherTreeNode>();
1561
1562            var allMethods = new[] { callPattern.Method }
1563                .Concat(callPattern.Method.GetInheritanceChain().Where(m => m.DeclaringType.IsInterface));
1564            foreach (var method in allMethods)
1565            {
1566                DebugView.TraceEvent(IndentLevel.MethodMatch, () => String.Format("Inspect arrangements on {0} on {1}", method, method.DeclaringType));
1567                if (!arrangementTreeRoots.TryGetValue(method, out arrangeFuncRoot))
1568                    continue;
1569
1570                var results = arrangeFuncRoot.GetMethodMock(callPattern);
1571                methodMockNodes.AddRange(results);
1572            }
1573
1574            var methodMock = GetMethodMockFromNodes(methodMockNodes, invocation);
1575            if (methodMock == null)
1576            {
1577                DebugView.TraceEvent(IndentLevel.MethodMatch, () => "No arrangement chosen");
1578                return null;
1579            }
1580
1581            DebugView.TraceEvent(IndentLevel.MethodMatch, () => String.Format("Chosen arrangement (id={0}) {1}",
1582                methodMockNodes.First(node => node.MethodMock == methodMock).Id, methodMock.ArrangementExpression));
1583
1584            methodMock.IsUsed = true; //used to correctly determine inSequence arranges
1585
1586            var behaviorsToProcess = GetBehaviorsToProcess(invocation, methodMock);
1587            foreach (var behavior in behaviorsToProcess)
1588            {
1589                behavior.Process(invocation);
1590            }
1591
1592            return methodMock;
1593        }
1594
1595        private static List<IBehavior> GetBehaviorsToProcess(Invocation invocation, IMethodMock methodMock)
1596        {
1597            var behaviorsToExecute = new List<IBehavior>();
1598
1599            var behaviorTypesToSkip = GetBehaviorTypesToSkip(invocation);
1600            behaviorsToExecute.AddRange(
1601                methodMock.Behaviors.Where(behavior => !behaviorTypesToSkip.Contains(behavior.GetType())));
1602
1603            var mock = invocation.MockMixin;
1604            if (mock != null)
1605            {
1606                behaviorsToExecute.AddRange(mock.SupplementaryBehaviors);
1607
1608#if !PORTABLE
1609                // explicitly add recursive mocking behavior for ref returns in order to set invocation result
1610                if (invocation.Method.GetReturnType().IsByRef)
1611                {
1612                    behaviorsToExecute.AddRange(
1613                        mock.FallbackBehaviors.Where(
1614                            behavior =>
1615                                behavior is CallOriginalBehavior
1616                                || (behavior is RecursiveMockingBehavior && ((RecursiveMockingBehavior)behavior).Type != RecursiveMockingBehaviorType.OnlyDuringAnalysis)));
1617                }
1618#endif
1619            }
1620
1621            return behaviorsToExecute;
1622        }
1623
1624        private static List<Type> GetBehaviorTypesToSkip(Invocation invocation)
1625        {
1626            var behaviorTypesToSkip = new List<Type>();
1627
1628            if (invocation.InAssertSet)
1629            {
1630                behaviorTypesToSkip.Add(typeof(InvocationOccurrenceBehavior));
1631            }
1632
1633            return behaviorTypesToSkip;
1634        }
1635
1636        private bool TryCreateDelegate(Type type, MockCreationSettings settings, out object delegateResult)
1637        {
1638            delegateResult = null;
1639            if (!typeof(Delegate).IsAssignableFrom(type) || type == typeof(Delegate) || type == typeof(MulticastDelegate))
1640                return false;
1641
1642            var backendType = mockFactory.CreateDelegateBackend(type);
1643            var backend = Create(backendType, settings);
1644
1645            delegateResult = Delegate.CreateDelegate(type, backend, backendType.GetMethod("Invoke"));
1646            return true;
1647        }
1648
1649        private IMethodMock GetMethodMockFromNodes(List<MethodMockMatcherTreeNode> methodMockNodes, Invocation invocation)
1650        {
1651            if (methodMockNodes.Count == 0)
1652            {
1653                return null;
1654            }
1655
1656            var resultList =
1657                methodMockNodes
1658                    .OrderBy(x => x.Id)
1659                    .Select(
1660                        x =>
1661                            new
1662                            {
1663                                x.MethodMock,
1664                                Acceptable = new Lazy<bool>(() => x.MethodMock.AcceptCondition == null
1665                                    || (bool)x.MethodMock.AcceptCondition.CallOverride(invocation))
1666                            })
1667                    .ToList();
1668
1669            var isInOrderOverwrites =
1670                resultList.Count() > 0
1671                && resultList.Where(x => x.MethodMock.IsInOrder).Any()
1672                && resultList.Last().MethodMock.IsInOrder;
1673
1674            // if one or more InOrder arrangement overwrites other ones, then skip selecting others
1675            if (!isInOrderOverwrites)
1676            {
1677                var nonSequentialOrInOrder =
1678                    resultList.Where(x => !x.MethodMock.IsSequential && !x.MethodMock.IsInOrder && x.Acceptable).LastOrDefault();
1679                if (nonSequentialOrInOrder != null)
1680                {
1681                    return nonSequentialOrInOrder.MethodMock;
1682                }
1683            }
1684
1685            var inOrder = resultList.Where(x => x.MethodMock.IsInOrder && !x.MethodMock.IsUsed && x.Acceptable).FirstOrDefault();
1686            if (inOrder != null)
1687            {
1688                return inOrder.MethodMock;
1689            }
1690
1691            var sequential = resultList.Where(x => x.MethodMock.IsSequential && !x.MethodMock.IsUsed && x.Acceptable).FirstOrDefault();
1692            if (sequential != null)
1693            {
1694                return sequential.MethodMock;
1695            }
1696
1697            return resultList.Where(x => (x.MethodMock.IsSequential || x.MethodMock.IsInOrder) && x.Acceptable).Select(x => x.MethodMock).LastOrDefault();
1698        }
1699
1700        internal string GetDebugView(object mock = null)
1701        {
1702            IEnumerable<MethodMockMatcherTreeNode> nodes;
1703            if (mock != null)
1704            {
1705                nodes = GetMethodMocksFromObject(mock);
1706            }
1707            else
1708            {
1709                nodes = from kvp in this.arrangementTreeRoots
1710                        let universalCP = CallPattern.CreateUniversalCallPattern(kvp.Key)
1711                        from node in kvp.Value.GetAllMethodMocks(universalCP)
1712                        select node;
1713            }
1714
1715            var sb = new StringBuilder();
1716            sb.AppendFormat("Elevated mocking: {0}\n", ProfilerInterceptor.IsProfilerAttached ? "enabled" : "disabled");
1717
1718            sb.AppendLine("\nArrangements and expectations:");
1719            bool addedStuff = false;
1720            foreach (var node in nodes)
1721            {
1722                addedStuff = true;
1723                var methodMock = node.MethodMock;
1724                sb.AppendFormat("    Arrangement (id={1}) {0}:\n", methodMock.ArrangementExpression, node.Id);
1725                foreach (var behavior in methodMock.Behaviors.OfType<IAssertableBehavior>())
1726                {
1727                    var debugView = behavior.DebugView;
1728                    if (debugView != null)
1729                        sb.AppendFormat("        {0}\n", debugView);
1730                }
1731            }
1732            if (!addedStuff)
1733                sb.AppendLine("    --none--");
1734
1735            sb.AppendLine("\nInvocations:");
1736            var invocations =
1737                (from kvp in this.invocationTreeRoots
1738                 let universalCP = CallPattern.CreateUniversalCallPattern(kvp.Key)
1739                 from node in kvp.Value.GetOccurences(universalCP)
1740                 select node).ToList();
1741
1742            addedStuff = false;
1743            foreach (var str in invocations.Select(inv => inv.GetDebugView()).OrderBy(_ => _))
1744            {
1745                addedStuff = true;
1746                sb.AppendFormat("    {0}\n", str);
1747            }
1748            if (!addedStuff)
1749                sb.AppendLine("    --none--");
1750
1751            return sb.ToString();
1752        }
1753
1754        /// <summary>
1755        /// An implementation detail. Not intended for external usage.
1756        /// Use this class for creating baseless proxies instead of typeof(object)
1757        /// so that you don't accidentally enable the interception of Object which kills performance
1758        /// </summary>
1759        public abstract class ExternalMockMixin
1760        { }
1761
1762        internal string GetRepositoryPath(string delimiter = "/")
1763        {
1764            var parentIds = new Queue<int>();
1765            var parentRepo = this.parentRepository;
1766            while (parentRepo != null)
1767            {
1768                parentIds.Enqueue(parentRepo.repositoryId);
1769                parentRepo = parentRepo.parentRepository;
1770            }
1771
1772            return delimiter + string.Join(delimiter, parentIds.Reverse());
1773        }
1774    }
1775}
1776
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Most used methods in MatcherInfo

Run Selenium Automation Tests on LambdaTest Cloud Grid

Trigger Selenium automation tests on a cloud-based Grid of 3000+ real browsers and operating systems.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)