Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FormRecognizer] Created sample for mocking #18890

Merged
merged 7 commits into from
Feb 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sdk/formrecognizer/Azure.AI.FormRecognizer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## 3.1.0-beta.3 (Unreleased)

## New Features
- Added protected constructors for mocking to `Operation` types, such as `TrainingOperation` and `RecognizeContentOperation`.
kinelski marked this conversation as resolved.
Show resolved Hide resolved

## 3.1.0-beta.2 (2021-02-09)
### Breaking changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ public enum LengthUnit
}
public partial class RecognizeBusinessCardsOperation : Azure.Operation<Azure.AI.FormRecognizer.Models.RecognizedFormCollection>
{
protected RecognizeBusinessCardsOperation() { }
public RecognizeBusinessCardsOperation(string operationId, Azure.AI.FormRecognizer.FormRecognizerClient client) { }
public override bool HasCompleted { get { throw null; } }
public override bool HasValue { get { throw null; } }
Expand All @@ -341,6 +342,7 @@ public RecognizeBusinessCardsOperation(string operationId, Azure.AI.FormRecogniz
}
public partial class RecognizeContentOperation : Azure.Operation<Azure.AI.FormRecognizer.Models.FormPageCollection>
{
protected RecognizeContentOperation() { }
public RecognizeContentOperation(string operationId, Azure.AI.FormRecognizer.FormRecognizerClient client) { }
public override bool HasCompleted { get { throw null; } }
public override bool HasValue { get { throw null; } }
Expand All @@ -354,6 +356,7 @@ public RecognizeContentOperation(string operationId, Azure.AI.FormRecognizer.For
}
public partial class RecognizeCustomFormsOperation : Azure.Operation<Azure.AI.FormRecognizer.Models.RecognizedFormCollection>
{
protected RecognizeCustomFormsOperation() { }
public RecognizeCustomFormsOperation(string operationId, Azure.AI.FormRecognizer.FormRecognizerClient client) { }
public override bool HasCompleted { get { throw null; } }
public override bool HasValue { get { throw null; } }
Expand Down Expand Up @@ -381,6 +384,7 @@ internal RecognizedFormCollection() : base (default(System.Collections.Generic.I
}
public partial class RecognizeInvoicesOperation : Azure.Operation<Azure.AI.FormRecognizer.Models.RecognizedFormCollection>
{
protected RecognizeInvoicesOperation() { }
public RecognizeInvoicesOperation(string operationId, Azure.AI.FormRecognizer.FormRecognizerClient client) { }
public override bool HasCompleted { get { throw null; } }
public override bool HasValue { get { throw null; } }
Expand All @@ -394,6 +398,7 @@ public RecognizeInvoicesOperation(string operationId, Azure.AI.FormRecognizer.Fo
}
public partial class RecognizeReceiptsOperation : Azure.Operation<Azure.AI.FormRecognizer.Models.RecognizedFormCollection>
{
protected RecognizeReceiptsOperation() { }
public RecognizeReceiptsOperation(string operationId, Azure.AI.FormRecognizer.FormRecognizerClient client) { }
public override bool HasCompleted { get { throw null; } }
public override bool HasValue { get { throw null; } }
Expand Down Expand Up @@ -458,6 +463,7 @@ internal CopyAuthorization() { }
}
public partial class CopyModelOperation : Azure.Operation<Azure.AI.FormRecognizer.Training.CustomFormModelInfo>
{
protected CopyModelOperation() { }
public CopyModelOperation(string operationId, string targetModelId, Azure.AI.FormRecognizer.Training.FormTrainingClient client) { }
public override bool HasCompleted { get { throw null; } }
public override bool HasValue { get { throw null; } }
Expand All @@ -471,10 +477,12 @@ public CopyModelOperation(string operationId, string targetModelId, Azure.AI.For
}
public partial class CreateComposedModelOperation : Azure.AI.FormRecognizer.Training.CreateCustomFormModelOperation
{
public CreateComposedModelOperation(string operationId, Azure.AI.FormRecognizer.Training.FormTrainingClient client) : base (default(string), default(Azure.AI.FormRecognizer.Training.FormTrainingClient)) { }
protected CreateComposedModelOperation() { }
public CreateComposedModelOperation(string operationId, Azure.AI.FormRecognizer.Training.FormTrainingClient client) { }
kinelski marked this conversation as resolved.
Show resolved Hide resolved
}
public partial class CreateCustomFormModelOperation : Azure.Operation<Azure.AI.FormRecognizer.Training.CustomFormModel>
{
protected CreateCustomFormModelOperation() { }
public CreateCustomFormModelOperation(string operationId, Azure.AI.FormRecognizer.Training.FormTrainingClient client) { }
public override bool HasCompleted { get { throw null; } }
public override bool HasValue { get { throw null; } }
Expand Down Expand Up @@ -581,7 +589,8 @@ public TrainingFileFilter() { }
}
public partial class TrainingOperation : Azure.AI.FormRecognizer.Training.CreateCustomFormModelOperation
{
public TrainingOperation(string operationId, Azure.AI.FormRecognizer.Training.FormTrainingClient client) : base (default(string), default(Azure.AI.FormRecognizer.Training.FormTrainingClient)) { }
protected TrainingOperation() { }
public TrainingOperation(string operationId, Azure.AI.FormRecognizer.Training.FormTrainingClient client) { }
}
public partial class TrainingOptions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Mock a client for testing using the Moq library

This sample illustrates how to use [Moq][moq] to create a unit test that mocks the response from a `FormRecognizerClient` method. For more examples of mocking, see [Moq samples][moq_samples].

## Define a method that uses a FormRecognizerClient
To show the usage of mocks, define a method that will be tested with mocked objects. For this case, assume we have a pre-trained custom model that's able to recognize groceries list. We are going to create a method that will calculate whether the total price of a list is expensive (total price > $100), only if the recognized field has a confidence greater than 70%.

```C# Snippet:FormRecognizerMethodToTest
private static async Task<bool> IsExpensiveAsync(string modelId, Uri documentUri, FormRecognizerClient client)
{
RecognizeCustomFormsOperation operation = await client.StartRecognizeCustomFormsFromUriAsync(modelId, documentUri);

Response<RecognizedFormCollection> response = await operation.WaitForCompletionAsync();
RecognizedForm form = response.Value[0];

if (form.Fields.TryGetValue("totalPrice", out FormField totalPriceField)
&& totalPriceField.Value.ValueType == FieldValueType.Float)
{
return totalPriceField.Confidence > 0.7f && totalPriceField.Value.AsFloat() > 100f;
}
else
{
return false;
}
}
```

## Create and setup mocks
To start, create a mock for the `FormRecognizerClient`. Most methods in this service make use of [Long-Running Operations][lros], so you'll likely need to create a mock operation as well.

```C# Snippet:FormRecognizerCreateMocks
var mockClient = new Mock<FormRecognizerClient>();
var mockOperation = new Mock<RecognizeCustomFormsOperation>();
```

Then, set up the client methods that will be executed. In this case, we will call the `StartRecognizeCustomFormsFromUriAsync` method.

```C# Snippet:FormRecognizerSetUpClientMock
var fakeModelId = Guid.NewGuid().ToString();
var fakeDocumentUri = new Uri("https://fake.document.uri");

mockClient.Setup(c => c.StartRecognizeCustomFormsFromUriAsync(fakeModelId, fakeDocumentUri,
It.IsAny<RecognizeCustomFormsOptions>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(mockOperation.Object));
```

If you're mocking an operation object, you will also need to set up the methods that will be called from it. In this sample, we only need to set `WaitForCompletionAsync`.

```C# Snippet:FormRecognizerSetUpOperationMock
var labelDataBox = FormRecognizerModelFactory.FieldBoundingBox(new List<PointF>()
{ new PointF(1f, 1f), new PointF(2f, 1f), new PointF(2f, 2f), new PointF(1f, 2f) });
var labelData = FormRecognizerModelFactory.FieldData(labelDataBox, 1, "Total price:", new List<FormElement>());

var valueDataBox = FormRecognizerModelFactory.FieldBoundingBox(new List<PointF>()
{ new PointF(4f, 1f), new PointF(5f, 1f), new PointF(5f, 2f), new PointF(4f, 2f) });
var valueData = FormRecognizerModelFactory.FieldData(valueDataBox, 1, "$150.00", new List<FormElement>());

var fieldValue = FormRecognizerModelFactory.FieldValueWithFloatValueType(150f);

var formField = FormRecognizerModelFactory.FormField("totalPrice", labelData, valueData, fieldValue, 0.85f);
var formPage = FormRecognizerModelFactory.FormPage(1, 8.5f, 11f, 0f, LengthUnit.Inch, new List<FormLine>(), new List<FormTable>());

var pageRange = FormRecognizerModelFactory.FormPageRange(1, 1);
var recognizedForm = FormRecognizerModelFactory.RecognizedForm("custom:groceries", pageRange,
new Dictionary<string, FormField>() { { "totalPrice", formField } },
new List<FormPage>() { formPage });
var recognizedFormCollection = FormRecognizerModelFactory.RecognizedFormCollection(new List<RecognizedForm>() { recognizedForm });

Response<RecognizedFormCollection> operationResponse = Response.FromValue(recognizedFormCollection, Mock.Of<Response>());

mockOperation.Setup(op => op.WaitForCompletionAsync(It.IsAny<CancellationToken>()))
.Returns(new ValueTask<Response<RecognizedFormCollection>>(operationResponse));
```

To keep the setup simple, we suggest that you only add the properties required by your tests when building your models. In this case, we only needed the `FormType`, the field's `Confidence`, and the field's `Value`, while the other properties could be set to `null`, `default`, or empty.
kinelski marked this conversation as resolved.
Show resolved Hide resolved

## Use mocks
Now, to validate if the groceries are expensive without making a network call, use the `FormRecognizerClient` mock.

```C# Snippet:FormRecognizerUseMocks
bool result = await IsExpensiveAsync(fakeModelId, fakeDocumentUri, mockClient.Object);
Assert.IsTrue(result);
```

[moq]: https://github.com/Moq/moq4/
[moq_samples]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer/samples
kinelski marked this conversation as resolved.
Show resolved Hide resolved
[lros]: https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/formrecognizer/Azure.AI.FormRecognizer#long-running-operations
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ internal CopyModelOperation(FormRecognizerRestClient serviceClient, ClientDiagno
Id = string.Join("/", substrs, substrs.Length - 3, 3);
}

/// <summary>
/// Initializes a new instance of the <see cref="CopyModelOperation"/> class. This constructor
/// is intended to be used for mocking only.
/// </summary>
protected CopyModelOperation()
{
}

/// <summary>
/// The last HTTP response received from the server.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,11 @@ internal CreateComposedModelOperation(string location, FormRecognizerRestClient
/// <param name="operationId">The ID of this operation.</param>
/// <param name="client">The client used to check for completion.</param>
public CreateComposedModelOperation(string operationId, FormTrainingClient client) : base(operationId, client) { }

/// <summary>
/// Initializes a new instance of the <see cref="CreateComposedModelOperation"/> class. This constructor
/// is intended to be used for mocking only.
/// </summary>
protected CreateComposedModelOperation() : base() { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ public CreateCustomFormModelOperation(string operationId, FormTrainingClient cli
_serviceClient = client.ServiceClient;
}

/// <summary>
/// Initializes a new instance of the <see cref="CreateCustomFormModelOperation"/> class. This constructor
/// is intended to be used for mocking only.
/// </summary>
protected CreateCustomFormModelOperation()
{
}

/// <summary>
/// Calls the server to get updated status of the long-running operation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ internal RecognizeBusinessCardsOperation(FormRecognizerRestClient serviceClient,
Id = operationLocation.Split('/').Last();
}

/// <summary>
/// Initializes a new instance of the <see cref="RecognizeBusinessCardsOperation"/> class. This constructor
/// is intended to be used for mocking only.
/// </summary>
protected RecognizeBusinessCardsOperation()
{
}

/// <summary>
/// Periodically calls the server till the long-running operation completes.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ internal RecognizeContentOperation(FormRecognizerRestClient serviceClient, Clien
Id = operationLocation.Split('/').Last();
}

/// <summary>
/// Initializes a new instance of the <see cref="RecognizeContentOperation"/> class. This constructor
/// is intended to be used for mocking only.
/// </summary>
protected RecognizeContentOperation()
{
}

/// <summary>
/// The last HTTP response received from the server.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ public RecognizeCustomFormsOperation(string operationId, FormRecognizerClient cl
Id = operationId;
}

/// <summary>
/// Initializes a new instance of the <see cref="RecognizeCustomFormsOperation"/> class. This constructor
/// is intended to be used for mocking only.
/// </summary>
protected RecognizeCustomFormsOperation()
{
}

/// <summary>
/// Calls the server to get updated status of the long-running operation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ internal RecognizeInvoicesOperation(FormRecognizerRestClient serviceClient, Clie
Id = operationLocation.Split('/').Last();
}

/// <summary>
/// Initializes a new instance of the <see cref="RecognizeInvoicesOperation"/> class. This constructor
/// is intended to be used for mocking only.
/// </summary>
protected RecognizeInvoicesOperation()
{
}

/// <summary>
/// Periodically calls the server till the long-running operation completes.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ internal RecognizeReceiptsOperation(FormRecognizerRestClient serviceClient, Clie
Id = operationLocation.Split('/').Last();
}

/// <summary>
/// Initializes a new instance of the <see cref="RecognizeReceiptsOperation"/> class. This constructor
/// is intended to be used for mocking only.
/// </summary>
protected RecognizeReceiptsOperation()
{
}

/// <summary>
/// Periodically calls the server till the long-running operation completes.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,11 @@ internal TrainingOperation(string location, FormRecognizerRestClient allOperatio
/// <param name="operationId">The ID of this operation.</param>
/// <param name="client">The client used to check for completion.</param>
public TrainingOperation(string operationId, FormTrainingClient client) : base(operationId, client) { }

/// <summary>
/// Initializes a new instance of the <see cref="TrainingOperation"/> class. This constructor
/// is intended to be used for mocking only.
/// </summary>
protected TrainingOperation() : base() { }
}
}
Loading