/// <summary>
        /// Expect a request to have been made which matches the given method and Uri, and return its mock.
        /// If multiple requests match, one will be returned. Each request may only be expected once and will only be returned once.
        /// If no matching request has been made, this method will throw a <see cref="InvalidOperationException"/>.
        /// </summary>
        /// <param name="method">The expected http method</param>
        /// <param name="uri">The expected uri</param>
        /// <returns>A <see cref="TestRequest"/> object matching the provided values</returns>
        public TestRequest Expect(HttpMethod method, Uri uri)
        {
            if (method == null)
            {
                throw new ArgumentNullException(nameof(method));
            }

            if (uri == null)
            {
                throw new ArgumentNullException(nameof(uri));
            }

            if (!uri.IsAbsoluteUri)
            {
                throw new ArgumentException($"The provided Uri is not absolute.", nameof(uri));
            }

            if (this.isDisposed)
            {
                throw new ObjectDisposedException("This object has previously been disposed.");
            }

            var expectation = new RequestExpectation(method, uri);

            return(this.httpMessageHandler.Expect(expectation));
        }
        public TestRequest Expect(RequestExpectation expectation)
        {
            AutoResetEvent autoResetEvent = null;

            try
            {
                lock (this.lockObj)
                {
                    // If the request was already made
                    if (this.TryGetMatchingItem(this.outstandingRequests, expectation, out var request))
                    {
                        return(request);
                    }

                    // The request hasn't been made yet, so create a wait handle to signal when the request does come in
                    autoResetEvent = new AutoResetEvent(false);
                    if (!this.outstandingExpectations.TryGetValue(expectation, out var matchingExpectations))
                    {
                        matchingExpectations = new Queue <AutoResetEvent>();
                        this.outstandingExpectations.Add(expectation, matchingExpectations);
                    }

                    matchingExpectations.Enqueue(autoResetEvent);
                }

                // Wait for the request to come in for some timeout period.
                if (!autoResetEvent.WaitOne(this.settings.ExpectationMatchTimeout))
                {
                    lock (this.lockObj)
                    {
                        throw new InvalidOperationException($"Expected request was not matched: [{expectation}]. Outstanding requests: [{this.GetOutstandingItemsDebugString(this.outstandingRequests)}]");
                    }
                }

                // It was signalled that the request was made, so try to get the matching request again. It should be there.
                lock (this.lockObj)
                {
                    if (this.TryGetMatchingItem(this.outstandingRequests, expectation, out var request))
                    {
                        return(request);
                    }
                    else
                    {
                        throw new InvalidOperationException(
                                  $"A request matching an outstanding expectation was made, but the request could not be found. This is likely a bug. Please report this to the developer. " +
                                  $"Expectation: [{expectation}]; " +
                                  $"Outstanding expectations: [{this.GetOutstandingItemsDebugString(this.outstandingExpectations)}]; " +
                                  $"Outstanding requests: [{this.GetOutstandingItemsDebugString(this.outstandingRequests)}]");
                    }
                }
            }
            finally
            {
                autoResetEvent?.Dispose();
            }
        }
        protected override Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var expectation          = new RequestExpectation(request.Method, request.RequestUri);
            var taskCompletionSource = new TaskCompletionSource <HttpResponseMessage>();
            var task = taskCompletionSource.Task;

            // Propagate request cancellation
            cancellationToken.Register(() => taskCompletionSource.TrySetCanceled(), useSynchronizationContext: false);

            // Add timeout to protect against awaiting on this task without a matching expect
            const string TimeoutErrorMessageFormat = "The mock request [{0}] timed out. This either means that you are awaiting this http request task without " +
                                                     "a matching expectation or your code is taking longer than the provided HttpClientTestingFactorySettings.RequestTimeout.";
            var timeoutCancellationSource = new CancellationTokenSource(this.settings.RequestTimeout);

            timeoutCancellationSource.Token.Register(() => taskCompletionSource.TrySetException(new TimeoutException(string.Format(TimeoutErrorMessageFormat, expectation))), useSynchronizationContext: false);

            // Add to the outstanding requests
            var testRequest = new TestRequest(request, taskCompletionSource);

            lock (this.lockObj)
            {
                if (!this.outstandingRequests.TryGetValue(expectation, out var matchingRequests))
                {
                    matchingRequests = new Queue <TestRequest>();
                    this.outstandingRequests.Add(expectation, matchingRequests);
                }

                matchingRequests.Enqueue(testRequest);

                // Signal a waiting expectation, if one exists
                if (this.TryGetMatchingItem(this.outstandingExpectations, expectation, out var autoResetEvent))
                {
                    autoResetEvent.Set();
                }
            }

            return(task);
        }
 // Only call when holding the lock
 private bool TryGetMatchingItem <T>(Dictionary <RequestExpectation, Queue <T> > items, RequestExpectation expectation, out T item)
 {
     if (items.TryGetValue(expectation, out var matchingItems) && matchingItems.Count > 0)
     {
         item = matchingItems.Dequeue();
         return(true);
     }
     else
     {
         item = default(T);
         return(false);
     }
 }