-
Notifications
You must be signed in to change notification settings - Fork 0
Documentation
By default RestVerifier will check every public method in your class. Methods from base classes will be skipped. You can change this scope with GetMethods
protected override void ConfigureVerifier(IGlobalSetupStarter<FileDataService> builder)
{
builder.GetMethods(type =>
{
return type.GetMethods(BindingFlags.Public);
});
}If you don't want to verify (invoke) specific method you can do this:
protected override void ConfigureVerifier(IGlobalSetupStarter<FileDataService> builder)
{
builder.ConfigureSetup(x =>
{
x.Setup(g => g.DeleteFile(Behavior.Generate<Guid>())).Skip();
});
}In this case RestVerifier will check all methods returned by GetMethods except DeleteFile.
By default, RestVerifier uses AutoFixture library for creating test data. For most types, it works great without any additional configuration. But for some types, we need to provide more information, how to create test values. One approach is to create your own Object Creator class inherits from the standard one and override Configure method. There you have an access to AutoFixture
public class NoteBookAppObjectCreator:AutoFixtureObjectCreator
{
protected override void Configure(Fixture fixture)
{
base.Configure(fixture);
fixture.Register<byte[], Stream>((byte[] data) => new MemoryStream(data));
fixture.Register<byte[], MemoryStream>((byte[] data) => new MemoryStream(data));
fixture.Customizations.Add(
new TypeRelay(
typeof(System.IO.Stream),
typeof(MemoryStream)));
}
}Next step is to configure RestVerifier to use this class
protected override void ConfigureVerifier(IGlobalSetupStarter<FileDataService> builder)
{
builder.UseObjectCreator<NoteBookAppObjectCreator>();
}Detailed info about AutoFixture configuration you can find here
By default, RestVerifier uses FluentAssertions library for objects comparision. For most types, default implementation works great, but of course sometimes our WebAPI uses special types which we should compare differently. To override a way how RestVerifier compares objects, you can create your own Comparer Class inherits from FluentAssertionComparer and override Compare method.
public class NoteBookAppAssertionComparer : FluentAssertionComparer
{
public override void Compare(object obj1, object obj2)
{
if (obj1 is UploadAvatarParameter fc1)
{
obj1 = fc1.Meta;
}
base.Compare(obj1, obj2);
}
}The last step is to configure RestVerifier to use this class
protected override void ConfigureVerifier(IGlobalSetupStarter<FileDataService> builder)
{
builder.UseComparer<NoteBookAppAssertionComparer>();
}Detailed info about FluentAssertions configuration you can find here
Be default RestVerifier is in Loose mode. In this mode, all methods returned by GetMethods will be executed and tested. To skip a specific method, you can mark it as Skip().
If you want to have a better control on what methods should be tested (probably you would like to run only one method), then you can switch to Strict mode. In this mode, RestVerifier will test only methods configured in ConfigureVerify or ConfigureSetup section. So basically you need to provide a list of methods to test. Of course still methods you configure must be included in GetMethods!
Example
GetMethods returns:
GetMethod1
GetMethod2
GetMethod3
GetMethod4
Configuration
_builder.ConfigureVerify(x =>
{
x.Verify(b => b.GetMethod1(Behavior.Transform<JobDTO>(h => h.Id)));
});
_builder.ConfigureSetup(x =>
{
x.Setup(b => b.GetMethod3(Behavior.Generate<PersonDTO>()));
});In **Loose **mode all methods will be tested GetMethod1 GetMethod2 GetMethod3 GetMethod4
In Strict mode only configured methods will be tested: GetMethod1 GetMethod3
By default, RestVerifier generates random (test) data for every method parameters. If you want to invoke a method with specific values, you can:
protected override void ConfigureVerifier(IGlobalSetupStarter<FileDataService> builder)
{
builder.ConfigureSetup(x =>
{
x.Setup(g => g.UploadAvatarFull(Behavior.Generate<UploadFileParam>(), new MemoryStream()));
});
}In this example first value will be generated but second is specified as an empty MemoryStream;
RestVerifier assumes that every client method parameter will be sent to the WebAPI and therefore generate a value for every parameter and register it for verification. Then on the ASP.NET side it will verify if all transfered parameters are equals. Basically if your client method has 2 parameters, then RestVerifier expects that in your controller you will also have two parameters. But sometimes this is not the case. You can inform RestVerifier that specific parameter should be ignore (it will not be verified on the server side):
For example:
Client side
Task UploadAvatarFull(UploadFileParam fileParam, Stream fileContent);ASP.NET
[HttpPost("uploadAvatarFull")]
public async Task<IActionResult> UploadAvatarFull([FromBody]UploadFileParam? meta )Only first parameter is send to the server and should be check. Second (Stream) should be ignored. Here is a configuration:
Configuration
protected override void ConfigureVerifier(IGlobalSetupStarter<FileDataService> builder)
{
builder.ConfigureVerify(x =>
{
x.Verify(g => g.UploadAvatarFull(Behavior.Verify<UploadFileParam>(), Behavior.Ignore<Stream>()));
});
}Sometime our client method doesn't send a default value of a parameter. In this case we need to inform RestVerifier to skip verifying this parameter in such situation.
For example:
Client side
public async Task<int> GetStatus(int value,byte testMode)
{
var url = GetUrl($"weatherforecast/GetStatus?value={value}");
if (testMode!=0)
{
url = QueryHelpers.AddQueryString(url, "testMode", testMode.ToString());
}
return await Execute(async httpClient =>
{
var res = await httpClient.GetFromJsonAsync<int>(url, CreateOptions()).ConfigureAwait(false);
return res!;
}).ConfigureAwait(false);
}ASP.NET
[HttpGet("GetStatus")]
public int GetStatus(int value, byte testMode)As you can see int client method, if testMode is 0 then we do not add this value to query string. In this case we want RestVerifier check this parameter for every values except 0. If test value for this parameter will be 0 (test data are random) then this parameter should be ignored.
Configuration
_builder.ConfigureVerify(x =>
{
x.Verify(b => b.GetStatus(Behavior.Verify<int>(),Behavior.Verify<byte>(0)));
});In many cases in client code we use complex type as a parameter value, but we send a single value to the Server (for example ID).
Client side
Task DeletePerson(PersonDTO person);ASP.NET
[HttpDelete("deletePerson")]
public async Task<IActionResult> DeletePerson(Guid id )Configuration
protected override void ConfigureVerifier(IGlobalSetupStarter<NoteDataService> builder)
{
builder.ConfigureVerify(x =>
{
x.Verify(v => v.DeletePerson(Behavior.Transform<PersonDTO>(o => o.Id)));
});
}Sometimes on the client side we have a few parameters but on the Server side we have one class with properties representing all those parameters.
Client side
Task UploadAvatarFull(UploadFileParam fileParam, Stream fileContent)ASP.NET
public class UploadAvatarParameter
{
public Stream? File { get; set; }
public UploadFileParam? Meta { get; set; }
}
[HttpPost("uploadAvatarFull")]
public async Task<IActionResult> UploadAvatarFull(UploadAvatarParameter uploadParam)Configuration
protected override void ConfigureVerifier(IGlobalSetupStarter<FileDataService> builder)
{
builder.ConfigureVerify(cons =>
{
cons.Verify(g => g.UploadAvatarFull(Behavior.Verify<UploadFileParam>(), Behavior.Verify<Stream>()))
.Transform<UploadFileParam,Stream>((p1, p2) =>
{
var param = new UploadAvatarParameter()
{
Meta = p1,
File = p2
};
return new[] { param };
});
});
}In this example we inform RestVerifier to create an instance of UploadAvatarParameter class and fill it with the client parameters;
If you have many client methods where we should ignore the same parameter type, we can configure this globaly.
Client side
Task<Guid> TestMethod1(ManualDTO manual,FileMetaData fileParam);
Task<string> TestMethod2(ManualDTO manual,PersonDTO person,DateTime date);ASP.NET
[HttpPost]
public async Task<IActionResult> TestMethod1([FromBody] FileMetaData? uploadParam)
{
...
}
[HttpPost]
public async Task<IActionResult> TestMethod2([FromBody] PersonDTO person,[FromQuery]DateTime date)
{
...
}Configuration
builder.ConfigureVerify(cons =>
{
config.Transform((paramInfo, paramValue) =>
{
if (paramValue.Value is ManualDTO)
{
paramValue.Ignore = true;
}
});
});In this example we have two methods with ManualDTO parameter. Do need to ignore them in verification process. We can do this globally (per value Type)
By default RestVerifier use a position of every parameter to determine which client parameter compare to specific WebAPI param. For example:
Client
public async Task<string> ParametersOrder(string address,string name)
{
var url = GetUrl($"weatherforecast/ParametersOrder?address={address}&name={name}");
return await Execute(async httpClient =>
{
var res = await httpClient.GetFromJsonAsync<string>(url, CreateOptions()).ConfigureAwait(false);
return res!;
}).ConfigureAwait(false);
}ASP.NET
[HttpGet("ParametersOrder")]
[Produces("text/json")]
public string ParametersOrder(string shortName,string newAddress)
{
return null;
}In the default RestVerifier configuration, position of both parameters will be used to match (names are not important). So in this case
name=shortName
address=newAddress
Of course sometimes this is incorrect, because parameters are places in different order:
Client
public async Task<string> ParametersOrder(string address,string name)
{
var url = GetUrl($"weatherforecast/ParametersOrder?address={address}&name={name}");
return await Execute(async httpClient =>
{
var res = await httpClient.GetFromJsonAsync<string>(url, CreateOptions()).ConfigureAwait(false);
return res!;
}).ConfigureAwait(false);
}ASP.NET
[HttpGet("ParametersOrder")]
[Produces("text/json")]
public string ParametersOrder(string address,string name)
{
return null;
}In this case first parameter on the client is name, but WebAPI on this position has address. If we use position rule than RestVerifier throw exception. To fix this problem, we can use Name matching strategy, where parameters are compared by names. To set names matching strategy you can use this code:
_builder.UseNameMatchingStrategy();To be sure that position matching strategy is enabled, use this:
_builder.UsePositionMatchingStrategy();RestVerifier can also check if data returned from our WebAPI, are transferred correctly to the client. To do this, it needs to first generate a test data which will be sent from ASP.NET Action method. There are three steps in this action
- RestVerifier generate a test return value of type taken from the Client method return type
- This generated value is directly pass as a result of tested WebAPI method
- If your action method returns different type than Client method, you can use Returns transformation method to provide a convertion mechanism.
- Action method returns value but client code not:
Client side
async Task GetPerson(Guid id)ASP.NET
[HttpGet("GetPerson")]
public PersonDTO GetPerson(Guid id)In this example RestVerifier throws exception. If this is not a bug, you can use NoReturn().
Configuration
_builder.ConfigureVerify(x =>
{
x.Verify(b => b.GetPerson()).NoReturn();
});- Return type of Action method and client method are different
Client side
Task<string> GetPersonName(Guid id)ASP.NET
[HttpGet("GetPerson")]
public PersonDTO GetPerson(Guid id)In this example RestVerifier throws exception. To handle this situation, you need to use Returns method and tell RestVerifier how to convert client return type to server return type
Configuration
_builder.ConfigureVerify(x =>
{
x.Verify(b => b.GetPersonName(Behavior.Verify<Guid>())).Returns<string>(v=>new PersonDTO{Name=v});
});- Exception has been thrown on the server side but client didn't throw exception
In this situation we assume that if any exception is throw on the server, client should react on this and also throw exception (type of the exception is not relevant).
- Request didn't reach WebAPI
When RestVerifier invoke your client methods, it assumes that this will send request to your WebAPI. If request is not send, then exception will be thrown.
There are cases when your WebAPI return some value and than your client code convert it to another type. In this case, we need to inform RestVerify, how to deal with this:
Client side
Task<Guid> UploadFile(FileMetaData fileParam, Stream fileContent);ASP.NET
[HttpPost]
public async Task<IActionResult> UploadFile([FromBody] FileMetaData? uploadParam)
{
return new FileAccessToken("url", "token", Guid.NewGuid());
}Configuration
builder.ConfigureVerify(cons =>
{
cons.Verify(g => g.UploadFile(Behavior.Verify<FileMetaData>(), Behavior.Ignore<Stream>()))
.Returns<Guid>(g =>
{
var token = new FileAccessToken("blob url", "test token",g);
return token;
});
});WebAPI returns type FileAccessToken but client code Guid only. In the configuration, we define, how to convert one type to another.
If we have many WebAPI controllers which returns a value with specific type and then on the client code we convert this value to another type, we can inform RestVerifier globally how to deal with this situation
Client side
Task<Stream> TestMethod1(Guid id);
Task<Stream> TestMethod2(Guid id,DateTime date);ASP.NET
[HttpGet]
public async Task<IActionResult> TestMethod1(Guid id)
{
...
return File(retValue.Content, retValue.MimeType,retValue.FileName);
}
[HttpGet]
public async Task<IActionResult> TestMethod2(Guid id,DateTime date)
{
...
return File(retValue.Content, retValue.MimeType,retValue.FileName);
}Configuration
builder.ConfigureVerify(cons =>
{
config.ReturnTransform<Stream>(b =>
{
var memory = (MemoryStream)b.Content;
var newMemory = new MemoryStream();
memory.CopyTo(newMemory);
newMemory.Position = 0;
memory.Position = 0;
return new FileStreamResult(memory, b.MimeType)
{
FileDownloadName = b.FileName
};
});
});In this case, our controller methods return FileStreamResult but client methods return Stream. In configuration we add a transformation for this situation and it will be used automatically for every method returns Stream
RestVerifier by default, checks a normal scenarios which is returning invoking WebAPI method without exceptions and return value. In this mode, RestVerifier instead of returning a value from WebAPI, it will throw exception. On the client side, it will check if client method also throw any exception. If not, this will be reported as a bug.
Configuration
_builder.CheckExceptionHandling<InvalidOperationException>();
_builder.CheckExceptionHandling<ArgumentNullException>();By turning on checking exception handling, RestVerifier first perform a standard test (normal scenario) and then throw and check every configured exception. Exception type provided in this method informs RestVerifier, what exception should be thrown.
By default, RestVerifier will check whether the thrown exception is correctly propagated to the client code which means that on the client side we expect the same exception type. If you want to change this behavior (for example in your client code you have a try...catch block and you wrap all exception to just one type, then you can configure RestVerifier to not to check the exception type:
_builder.ConfigureVerify(x =>
{
x.Verify(b => b.WrongExceptionHandling(Behavior.Transform<PersonDTO>(h => h.Id))).ExceptionHandling(ExceptionHandling.ThrowButDontCheckType);
});If you want to skip testing exception handling for specify method, use this configuration:
Configuration
_builder.ConfigureVerify(x =>
{
x.Verify(b => b.WrongExceptionHandling(Behavior.Transform<PersonDTO>(h => h.Id))).ExceptionHandling(ExceptionHandling.Ignore);
});