/// <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); } }