Skip to content

Documentation

robocik edited this page Dec 13, 2023 · 18 revisions

Global configuration

Specify methods to tests

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

Skip method

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.

Customize test data creator (AutoFixture)

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

Customize objects comparison (FluentAssertions)

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

Strict and Loose mode

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

Parameters

Specify test values

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;

Ignore parameter

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

Ignore parameter if test data has a specific value

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

Simple parameter transformation

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

Complex parameters transformation

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;

Global parameters transformation

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)

Parameters matching strategies

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();

Return Value

How RestVerifier return value from the WebAPI

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

  1. RestVerifier generate a test return value of type taken from the Client method return type
  2. This generated value is directly pass as a result of tested WebAPI method
  3. If your action method returns different type than Client method, you can use Returns transformation method to provide a convertion mechanism.

RestVerifier checks also potential problems:

  • 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.

Transform return value

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.

Global return value transformations

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

Exceptions

Enable checking exception handling

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

Suppressing checking exception handling for specified method

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