Skip to content

Usage Guide

Bas Dijkstra edited this page Nov 20, 2024 · 119 revisions

Welcome to the RestAssured.Net usage guide. RestAssured.Net is an idiomatic port of REST Assured for Java and brings the power of REST Assured to the C# .NET ecosystem.

Below you can find a long list of examples on how to use RestAssured.Net to write readable tests for your HTTP APIs. An even richer set of usage examples can be found in the RestAssured.Net.Tests project. The examples in that project double as acceptance tests for the library.

Getting started

  • Create a new test project using your testing framework of choice. RestAssured.Net targets .NET 6, .NET 7, .NET 8 and .NET 9.
  • Add the latest RestAssured.Net NuGet package to your project
  • Add the following using directive to your test class: using static RestAssured.Dsl;
  • Write and run your tests!

Response verification

HTTP status codes

Checking the HTTP status code of your responses can be done in several ways.

By specifying the expected status code as an integer

[Test]
public void StatusCodeIndicatingSuccessCanBeVerifiedAsInteger()
{
    Given()
    .When()
    .Get("http://localhost:9876/http-status-code-ok")
    .Then()
    .StatusCode(200);
}

By specifying the expected status code as an HttpStatusCode value

    .StatusCode(HttpStatusCode.OK);

Using an NHamcrest matcher

    .StatusCode(NHamcrest.Is.EqualTo(200));

Header values

By specifying the expected header value as a string

[Test]
public void MultipleResponseHeadersCanBeVerified()
{
    Given()
    .When()
    .Get("http://localhost:9876/custom-multiple-response-headers")
    .Then()
    .StatusCode(200)
    .And() // Example of using the And() syntactic sugar method in response verification.
    .Header("custom_header_name", "custom_header_value")
    .And()
    .Header("another_header", "another_value");
}

Using an NHamcrest matcher

    .Header("custom_header_name", NHamcrest.Contains.String("tom_header_val"));

The Content-Type header

By specifying the expected value as a string

[Test]
public void ResponseContentTypeHeaderCanBeVerified()
{
    Given()
    .When()
    .Get("http://localhost:9876/custom-response-content-type-header")
    .Then()
    .StatusCode(200)
    .ContentType("application/something");
}

Using an NHamcrest matcher

    .ContentType(NHamcrest.Contains.String("something"));

Response cookies

You can verify the value of a response cookie (including secure and HTTP-only cookies) using an NHamcrest matcher:

[Test]
public void GenericCookieValueCanBeVerified()
{
    Given()
    .When()
    .Get("http://localhost:9876/response-with-generic-cookie")
    .Then()
    .Cookie("my_cookie", NHamcrest.Is.EqualTo("my_value"));
}

Response body (elements)

Verifying the entire response body

You can check the entire response body as a plaintext string. JSON strings work as well.

[Test]
public void JsonStringResponseBodyCanBeVerified()
{
    Given()
    .When()
    .Get("http://localhost:9876/plaintext-response-body")
    .Then()
    .StatusCode(200)
    .Body("{\"id\": 1, \"user\": \"John Doe\"}");
}

Using an NHamcrest matcher

    .Body(NHamcrest.Contains.String("John Doe"));

Verifying individual element values

You can also select individual elements, or collections of elements, using either a JsonPath expression (for JSON responses) or an XPath expression (for XML and HTML responses).

These elements or element collections can then be verified using NHamcrest matchers. Here's a JSON example:

[Test]
public void JsonResponseBodyElementStringValueCanBeVerified()
{
    Given()
    .When()
    .Get("http://localhost:9876/json-response-body")
    .Then()
    .StatusCode(200)
    .Body("$.Places[0].Name", NHamcrest.Contains.String("City"));
}

or

    .Body("$.Places[0:].Name", NHamcrest.Has.Item(NHamcrest.Is.EqualTo("Sun City")));

and here's an example for XML:

[Test]
public void XmlResponseBodyElementStringValueCanBeVerifiedUsingNHamcrestMatcher()
{ 
    Given()
    .When()
    .Get("http://localhost:9876/xml-response-body")
    .Then()
    .StatusCode(200)
    .Body("//Place[1]/Name", NHamcrest.Is.EqualTo("Sun City"));
}

From version 2.6.0 onwards, you can also use XPath to verify HTML response body elements. See these tests for examples.

RestAssured.NET uses Json.NET or System.Xml to evaluate the expressions used to select elements from the response body.

By default, selection of the evaluator is made based on the value of the response Content-Type header. You can override this and force a specific evaluator to be used by passing in an additional argument to the Body() method (useful when the API you're testing sends incorrect Content-Type header values, for example):

.Body("$.places[0].name", NHamcrest.Is.EqualTo("Beverly Hills"), VerifyAs.Json)

Response times

You can verify the response time of the API call (measured using a System.Diagnostics.Stopwatch) against an NHamcrest matcher like this:

[Test]
public void ResponseTimeCanBeVerified()
{
    Given()
    .When()
    .Get($"http://localhost:9876/delayed-response")
    .Then()
    .ResponseTime(NHamcrest.Is.GreaterThan(TimeSpan.FromMilliseconds(200)));
}

Response body length

If you want to check that the response body length (calculated using the actual response body, not the value of the Content-Length header) matches certain expectations, you can do that using an NHamcrest matcher:

[Test]
public void ResponseBodyLengthCanBeVerified()
{
    Given()
    .When()
    .Get("http://localhost:9876/json-response-body")
    .Then()
    .Log(RestAssured.Response.Logging.ResponseLogLevel.All)
    .ResponseBodyLength(NHamcrest.Is.GreaterThan(25));
}

JSON schema validation

A JSON response body can be validated against a JSON schema like this:

[Test]
public void JsonSchemaCanBeSuppliedAndVerifiedAsString()
{
    Given()
    .When()
    .Get("http://localhost:9876/json-schema-validation")
    .Then()
    .StatusCode(200)
    .And()
    .MatchesJsonSchema(jsonSchema);
}

The jsonSchema object can be supplied either as a string or as an NJsonSchema.JsonSchema object. If supplied as a string, MatchesJsonSchema() will throw a ResponseVerificationException if the schema cannot be parsed by NJsonSchema.

XML schema and DTD validation

An XML response body can be validated against an XSD like this:

[Test]
public void XmlSchemaCanBeSuppliedAndVerifiedAsString()
{
    Given()
    .When()
    .Get("http://localhost:9876/xml-schema-validation")
    .Then()
    .StatusCode(200)
    .And()
    .MatchesXsd(xmlSchema);
}

The xmlSchema object can be supplied either as a string or as a System.Xml.Schema.XmlSchemaSet object. If supplied as a string, MatchesXsd() will throw a ResponseVerificationException if the schema cannot be parsed by System.Xml.

Additionally, an XML response can be validated against inline DTDs, too:

[Test]
public void XmlResponseCanBeVerifiedAgainstInlineDtd()
{
    Given()
    .When()
    .Get("http://localhost:9876/matching-dtd-validation")
    .Then()
    .StatusCode(200)
    .And()
    .MatchesInlineDtd();
}

Deserializing a response payload into a C# object

RestAssured.Net supports deserialization of both JSON and XML response payloads into C# objects.

For JSON, RestAssured.Net uses Json.NET as its deserializer. For XML, RestAssured.Net uses System.Xml.Serializer as its deserializer.

[Test]
public void ObjectCanBeDeserializedFromJson()
{
    Location responseLocation = (Location)Given()
    .When()
    .Get("http://localhost:9876/json-deserialization")
    .DeserializeTo(typeof(Location));

    Assert.That(responseLocation.Country, Is.EqualTo("United States"));
    Assert.That(responseLocation.Places?.Count, Is.EqualTo(2));
}

By default, selection of the deserializer is made based on the value of the response Content-Type header. You can override this and force a specific deserializer to be used by passing in a second argument (useful when the API you're testing sends incorrect Content-Type header values, for example):

.DeserializeTo(typeof(Location), DeserializeAs.Json)

If no appropriate deserializer can be determined, a DeserializationException is thrown.

If you want to, you can also perform some assertions on the response before deserializing:

[Test]
public void ObjectCanBeDeserializedFromJsonAfterVerifications()
{
    Location responseLocation = (Location)Given()
        .When()
        .Get("http://localhost:9876/json-deserialization")
        .Then()
        .StatusCode(200)
        .And()
        .DeserializeTo(typeof(Location));

    Assert.That(responseLocation.Country, Is.EqualTo("United States"));
    Assert.That(responseLocation.Places?.Count, Is.EqualTo(2));
}

As of version 3.0.0, you can also pass in custom Newtonsoft.Json.JsonSerializerSettings when deserializing an object from JSON:

[Test]
public void CustomJsonSerializerSettingsCanBeSuppliedWhenDeserializing()
{
    JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings();
    jsonSerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
    
    Place place = (Place)Given()
        .When()
        .Get("http://localhost:9876/object-deserialization-custom-settings")
        .Then()
        .UsingJsonSerializerSettings(jsonSerializerSettings)
        .DeserializeTo(typeof(Place));
}

Extracting values from the response

Extracting the entire response body

You can extract the entire response body as a string:

[Test]
public void ResponseBodyCanBeExtracted()
{
    string responseBodyAsString = Given()
        .When()
        .Get($"{MOCK_SERVER_BASE_URL}/json-response-body")
        .Then()
        .StatusCode(200)
        .Extract().Body();
}

Extracting JSON response body values

You can extract singular values from a JSON response body for later re-use using JsonPath:

[Test]
public void JsonResponseBodyElementStringValueCanBeExtracted()
{
    string placeName = (string)Given()
    .When()
    .Get("http://localhost:9876/json-response-body")
    .Then()
    .StatusCode(200)
    .Extract().Body("$.Places[0].Name");

    Assert.That(placeName, NUnit.Framework.Is.EqualTo("Sun City"));
}

This works for other primitive data types as well.

Note: for now, integer values need to be stored in a variable of type long (and then converted to int if you need it), as Json.NET deserializes values to Int64, not Int32. Suggestions (or even a PR) that address this are welcome.

Extracting multiple values works, too, but at the moment you have to store them in an object of type List<object>:

[Test]
public void JsonResponseBodyMultipleElementsCanBeExtracted()
{
    List<object> placeNames = (List<object>)Given()
    .When()
    .Get("http://localhost:9876/json-response-body")
    .Then()
    .StatusCode(200)
    .Extract().Body("$.Places[0:].IsCapital");

    Assert.That(placeNames.Count, NUnit.Framework.Is.EqualTo(2));
}

Extracting XML or HTML response body values

You can extract singular values from an XML response body for later re-use using XPath:

[Test]
public void XmlResponseBodyElementValueCanBeExtracted()
{
    string placeName = (string)Given()
    .When()
    .Get("http://localhost:9876/xml-response-body")
    .Then()
    .StatusCode(200)
    .Extract().Body("//Place[1]/Name");

    Assert.That(placeName, Is.EqualTo("Sun City"));
}

Note: for XML, all element values will be returned as a string (based on the InnerText property of System.Xml.XmlNode).

Extracting multiple values works, too, but at the moment you have to store them in an object of type List<string> for the same reasons:

[Test]
public void XmlResponseBodyMultipleElementsCanBeExtracted()
        {
    List<string> placeNames = (List<string>)Given()
    .When()
    .Get("http://localhost:9876/xml-response-body")
    .Then()
    .StatusCode(200)
    .Extract().Body("//Place/Name");

    Assert.That(placeNames.Count, Is.EqualTo(2));
}

From version 2.6.0 onwards, you can also extract HTML response body elements in the same way. See these tests for examples.

RestAssured.NET uses Json.NET or System.Xml to evaluate the expressions used to extract elements from the response body.

By default, selection of the evaluator is made based on the value of the response Content-Type header. You can override this and force a specific evaluator to be used by passing in an additional argument to the Body() method (useful when the API you're testing sends incorrect Content-Type header values, for example):

.Extract().Body("$.places[0].name", ExtractAs.Json)

Extracting response header values

[Test]
public void JsonResponseHeaderCanBeExtracted()
{
    string responseHeaderValue = Given()
    .When()
    .Get("http://localhost:9876/json-response-body")
    .Then()
    .StatusCode(200)
    .Extract().Header("custom_header");

    Assert.That(responseHeaderValue, NUnit.Framework.Is.EqualTo("custom_header_value"));
}

Extracting response cookies

You can extract the value of a response cookie (including secure and HTTP-only cookies) as a string:

[Test]
public void GenericCookieValueCanBeExtracted()
{
    string cookieValue = Given()
        .When()
        .Get("http://localhost:9876/response-with-generic-cookie")
        .Then()
        .Extract().Cookie("my_cookie");
}

Extracting the entire response

You can also get the entire response as an object of type System.Net.Http.HttpResponseMessage:

[Test]
public void EntireResponseCanBeExtracted()
{
    HttpResponseMessage response = Given()
    .When()
    .Get("http://localhost:9876/json-response-body")
    .Then()
    .StatusCode(200)
    .Extract().Response();

    Assert.That(response.StatusCode, NUnit.Framework.Is.EqualTo(HttpStatusCode.OK));
    Assert.That(response.Headers.GetValues("custom_header").First(), NUnit.Framework.Is.EqualTo("custom_header_value"));
}

Request specification

HTTP verbs

RestAssured.Net supports making HTTP calls using GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS. You can select the HTTP verb to be used by using the corresponding method (Get() for an HTTP GET, etc.).

Alternatively (and this is especially useful when checking for Broken Function Level Authorization), you can use Invoke(string endpoint, System.Net.Http.HttpMethod httpMethod) to invoke an endpoint with a specific HTTP verb.

Header values

Custom headers and their values

[Test]
public void HeaderWithASingleValueCanBeSupplied()
{
    Given()
    .Header("my_header", "my_header_value")
    .When()
    .Get("http://localhost:9876/single-header-value")
    .Then()
    .StatusCode(200);
}

Content-Type header and content encoding

    Given()
    .ContentType("application/xml")
    .ContentEncoding(Encoding.ASCII)

Accept header

    Given()
    .Accept("application/xml")

You can also set headers as part of a RequestSpecification.

Specifying query parameters

While you can add query parameters and their values to your tests by building the endpoint dynamically using string interpolation, string concatenation or even a StringBuilder, RestAssured.Net also provides two utility methods to make the process easier and your tests more readable:

[Test]
public void SingleQueryParameterCanBeSpecified()
{
    Given()
    .QueryParam("name", "john")
    .When()
    .Get("http://localhost:9876/single-query-param")
    .Then()
    .StatusCode(200);
}

The endpoint invoked here is http://localhost:9876/single-query-param?name=john.

To add multiple query parameters, you can call QueryParam() multiple times, or pass an object of type IEnumerable<KeyValuePair<string, object>> to QueryParams():

[Test]
public void MultipleQueryParametersCanBeSpecifiedUsingAListOfKeyValuePairs()
{
    var queryParams = new List<KeyValuePair<string, object>>
    {
        new KeyValuePair<string, object>("name", "ny_name"),
        new KeyValuePair<string, object>("id", "my_id"),
    };

    Given()
    .QueryParams(queryParams)
    .When()
    .Get("http://localhost:9876/multiple-query-params")
    .Then()
    .StatusCode(200);
}

You can also set query parameters as part of a RequestSpecification.

Specifying path parameters

Similar to query parameters, you can add a path parameter either by string interpolation, string concatenation or a StringBuilder, but RestAssured.Net provides utility methods for improved readability here, too:

[Test]
public void SinglePathParameterCanBeSpecified()
{
    Given()
    .PathParam("userid", 1)
    .When()
    .Get("http://localhost:9876/user/[userid]")
    .Then()
    .StatusCode(200);
}

To define the location of a path parameter, use a placeholder name that matches the one in the PathParam() definition and place it between square brackets ([ and ]). RestAssured.Net uses Stubble to create the path, which is http://localhost:9876/user/1 in this example.

To define multiple path parameters, call PathParam() multiple times, or pass a Dictionary to PathParams():

[Test]
public void MultiplePathParameterCanBeSpecifiedUsingADictionary()
{
    Dictionary<string, object> pathParams = new Dictionary<string, object>
    {
        { "userid", 1 },
        { "accountid", "NL1234" },
    };

    Given()
    .PathParams(pathParams)
    .When()
    .Get("http://localhost:9876/user/[userid]/account/[accountid]")
    .Then()
    .StatusCode(200);
}

Authentication details

Basic authentication

[Test]
public void HeaderWithASingleValueCanBeSupplied()
{
    Given()
    .BasicAuth("username", "password")
    .When()
    .Get("http://localhost:9876/basic-auth")
    .Then()
    .StatusCode(200);
}

OAuth2 authentication tokens

    Given()
    .OAuth2("this_is_my_token")

You can also set Basic and OAuth2 authentication details as part of a RequestSpecification.

NTLM authentication

    Given()
    .NtlmAuth()

This will set NTLM authentication to the value of System.Net.CredentialCache.DefaultNetworkCredentials. An overload is available that lets you set the username, password and domain yourself. All arguments default to an empty string.

Cookies

You can add a cookie to a request like this:

[Test]
public void CookieWithASingleValueCanBeSupplied()
{
    Given()
    .Cookie("my_cookie", "my_cookie_value")
    .When()
    .Get("http://localhost:9876/single-cookie-value")
    .Then()
    .StatusCode(200);
}

The Cookie() method has overloads that take either an argument of type System.Net.Cookie or of System.Net.CookieCollection, so those can be added, too. See the tests for cookies for examples.

Timeouts

For some requests, you might want to change the default timeout, which is the System.Net.Http.HttpClient default of 100,000 milliseconds:

[Test]
public void CustomTimeoutCanBeSuppliedInTest()
{
    Given()
    .Timeout(TimeSpan.FromSeconds(200))
    .When()
    .Get("http://localhost:9876/timeout-ok")
    .Then()
    .StatusCode(200);
}

You can also set a custom timeout in a RequestSpecification.

User agent

If you want to set the user agent for a specific HTTP request, you can do that by specifying the product name and product value like this:

[Test]
public void CustomUserAgentCanBeSuppliedUsingProductNameAndProductVersionInTest()
{
    Given()
    .UserAgent("MyUserAgent", "1.0")
    .When()
    .Get("http://localhost:9876/user-agent")
    .Then()
    .StatusCode(200);
}

An overload is available to allow passing in an object of type System.Net.Http.Headers.ProductInfoHeaderValue.

You can also set the user agent as part of a RequestSpecification.

Proxy

In case your API calls need to go through a proxy, you can specify those in your requests like this:

[Test]
public void ProxyCanBeSpecified()
{
    IWebProxy proxy = new WebProxy("http://yourproxy.com:80", true);

    Given()
    .Proxy(proxy)
    .When()
    .Get("http://localhost:9876/proxy")
    .Then()
    .StatusCode(200);
}

You can also set the proxy as part of a RequestSpecification.

Disabling SSL checks

In case you want to hit an HTTPS endpoint without having to care about SSL, you can turn off SSL validation on the HttpClient used by RestAssured.Net using DisableSslCertificateValidation():

[Test]
public void HttpsConnectionsCanBeUsed()
{
    Given()
    .DisableSslCertificateValidation()
    .When()
    .Get("https://localhost:8443/ssl-endpoint")
    .Then()
    .StatusCode(200);
}

You can also disable SSL certificate validation as part of a RequestSpecification, or globally through the static RestAssuredConfiguration.

Payload

Payload as a plaintext string

[Test]
public void PlaintextRequestBodyCanBeSupplied()
{
    Given()
    .Body("Here's a plaintext request body.")
    .When()
    .Post("http://localhost:9876/plaintext-request-body")
    .Then()
    .StatusCode(201);
}

Payload as a JSON string

    Given()
    .Body("{\"id\": 1, \"user\": \"John Doe\"}")

Serializing an object into JSON

RestAssured.Net uses Json.NET as its JSON serializer.

[Test]
public void ObjectCanBeSerializedToJson()
{
    Location location = new Location
    {
        Country = "United States",
        State = "California",
        ZipCode = 90210,
    };

    Given()
    .ContentType("application/json")
    .Body(location)
    .When()
    .Post("http://localhost:9876/json-serialization")
    .Then()
    .StatusCode(201);
}

This also works with a Dictionary:

[Test]
public void DictionaryCanBeSerializedToJson()
{
    Dictionary<string, object> post = new Dictionary<string, object>
    {
        { "Id", 1 },
        { "Title", "My post title" },
        { "Body", "My post body" },
    };

    Given()
    .Body(post)
    .When()
    .Post("http://localhost:9876/object-serialization")
    .Then()
    .StatusCode(201);
}

It works with anonymous types, too:

[Test]
public void AnonymousObjectCanBeSerializedToJson()
{
    var post = new
    {
        Id = 1,
        Title = "My post title",
        Body = "My post body",
    };

    Given()
    .Body(post)
    .When()
    .Post("http://localhost:9876/object-serialization")
    .Then()
    .StatusCode(201);
}

and with records:

private record BlogPostRecord(int id, string title, string body);

[Test]
public void RecordCanBeSerializedToJson()
{
    var post = new BlogPostRecord(this.blogPost.Id, this.blogPost.Title, this.blogPost.Body);

    Given()
        .Body(post)
        .When()
        .Post("http://localhost:9876/object-serialization")
        .Then()
        .StatusCode(201);
}

As of version 3.0.0, you can also pass in custom Newtonsoft.Json.JsonSerializerSettings when serializing an object to JSON:

[Test]
public void CustomJsonSerializerSettingsCanBeSuppliedInTestBody()
{
    JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings();
    jsonSerializerSettings.DateFormatString = "yyyy-MM-dd";

    var post = new
    {
        Id = 1,
        Title = "My post title",
        Date = new DateTime(1999, 12, 31, 23, 59, 59, 0),  // date will now show up as '1999-12-31' in the request
    };

    Given()
    .JsonSerializerSettings(jsonSerializerSettings)
    .Body(post)
    .When()
    .Post("http://localhost:9876/object-serialization-custom-settings")
    .Then()
    .StatusCode(201);
}

Serializing an object into XML

RestAssured.Net uses System.Xml.Serializer as its XML serializer.

[Test]
public void ObjectCanBeSerializedToXml()
{
    Location location = new Location
    {
        Country = "United States",
        State = "California",
        ZipCode = 90210,
    };

    Given()
    .ContentType("application/xml")
    .Body(location)
    .When()
    .Post("http://localhost:9876/xml-serialization")
    .Then()
    .StatusCode(201);
}

You can also serialize a C# record to XML. Please note that the record requires a parameterless constructor for this to work.

public record BlogPostRecord(int id, string title, string body)
{
    public BlogPostRecord()
        : this(0, string.Empty, string.Empty)
    {
    }
}

[Test]
public void RecordCanBeSerializedToXml()
{
    var post = new BlogPostRecord(123, "My blog post title", "My blog post body");

    Given()
        .ContentType("application/xml")
        .Body(post)
        .When()
        .Post("http://localhost:9876/xml-serialization-from-record")
        .Then()
        .StatusCode(201);
}

x-www-form-urlencoded form data

If you need to uploaded x-www-form-urlencoded form data, you can do that using the FormData() method:

[Test]
public void FormDataCanBeSupplied()
{
    var formData = new[]
    {
        new KeyValuePair<string, string>("name", "John Doe"),
        new KeyValuePair<string, string>("email", "[email protected]"),
    };

    Given()
    .FormData(formData)
    .When()
    .Post("http://localhost:9876/form-data")
    .Then()
    .StatusCode(201);
}

The resulting request payload for this example will be name=John+Doe&email=johndoe%40example.com.

Multipart form data

If you need to upload multipart form data, you can do that using the MultiPart() method:

[Test]
public void MultiPartFormDataWithDefaultControlNameCanBeSupplied()
{
    Given()
    .MultiPart(new FileInfo(@"C:\Users\Bas\some_file_to_upload.pdf"))
    .When()
    .Post("http://localhost:9876/simple-multipart-form-data")
    .Then()
    .StatusCode(201);
}

The default control name used in the multipart upload is file, and the MIME type will be automatically determined based on the file extension. You can control these values by supplying them as arguments to MultiPart(). See the tests for examples.

For other multipart data types, you can use the MultiPart(string name, HttpContent content) method for single data entities, or MultiPart(Dictionary<string, HttpContent> content) to add multiple entities in a single call. Again, the tests contain working examples.

GraphQL support

Next to support for 'plain' REST request payloads, RestAssured.Net also supports working with GraphQL APIs by means of the GraphQLRequest and GraphQLRequestBuilder objects:

[Test]
public void ParameterizedGraphQLQueryCanBeSupplied()
{
    string parameterizedQuery = @"query getRocketData($id: ID!)
            {
                rocket(id: $id) {
                    name
                    country
                }
            }";

    Dictionary<string, object> variables = new Dictionary<string, object>
    {
        { "id", "falcon1" },
    };

    GraphQLRequest request = new GraphQLRequestBuilder()
        .WithQuery(parameterizedQuery)
        .WithOperationName("getRocketData")
        .WithVariables(variables)
        .Build();

    Given()
    .GraphQL(request)
    .When()
    .Post("http://localhost:9876/graphql-with-variables")
    .Then()
    .StatusCode(200)
    .Body("$.data.rocket.country", NHamcrest.Is.EqualTo("Republic of the Marshall Islands"));
}

Using request specifications

With request specifications, you can supply common characteristics of requests to make in your tests in a single place:

private RequestSpecification? requestSpecification;

[SetUp]
public void CreateRequestSpecifications()
{
    this.requestSpecification = new RequestSpecBuilder()
        .WithBaseUri("http://localhost")
        .WithScheme("http") // deprecated
        .WithHostName("localhost") // deprecated
        .WithBasePath("api")
        .WithPort(9876)
        .WithQueryParam("param_name", "param_value")
        .WithTimeout(TimeSpan.FromSeconds(200))
        .WithUserAgent("MyUserAgent", "1.0")
        .WithProxy(new WebProxy("http://yourproxy.com:80", true)
        .WithHeader("my_request_spec_header", "my_request_spec_header_value")
        .WithContentType("application/json")
        .WithContentEncoding(Encoding.ASCII)
        .WithBasicAuth("username", "password")
        .WithOAuth2("this_is_my_token")
        .WithContentType("application/json")
        .WithContentEncoding(Encoding.ASCII)
        .WithDisabledSslCertificateValidation()
        .WithJsonSerializerSettings(new JsonSerializerSettings())
        .WithHttpCompletionOption(HttpCompletionOption.ResponseHeadersRead)
        .WithLogConfiguration(new LogConfiguration())
        .Build();
}

and then use those in your test:

[Test]
public void RequestSpecificationCanBeUsed()
{
    Given()
    .Spec(this.requestSpecification)
    .When()
    .Get("/request-specification")
    .Then()
    .StatusCode(200);
}

The test will use the scheme, host and port supplied in the request specification to construct the desired URL http://localhost:9876/api/request-specification. Here's the business logic:

  • If the endpoint passed to the method calling the endpoint (e.g., Get()) is passed a valid URI, the scheme, host and port set in the request specification will be ignored.
  • The hostname should be supplied without the scheme (http, https, etc.) to construct a valid URI.
  • Base paths can be supplied with or without a leading or trailing /.
  • Sensible defaults are used when no values are supplied: scheme defaults to http, hostname defaults to localhost, port defaults to -1 (to ensure that the default for the set scheme will be used), base path defaults to an empty string.

Logging request and response details

For debugging and exploratory testing purposes, you can log request and response details to the console:

[Test]
public void RequestDetailsCanBeWrittenToStandardOutputForJson()
{

    var logConfig = new LogConfiguration {
        RequestLogLevel = RequestLogLevel.All,
        ResponseLogLevel = ResponseLogLevel.All,
        SensitiveRequestHeadersAndCookies = new List<string>() { "SensitiveRequestHeader" },
    }

    Given()
    .Log(logConfig)
    .When()
    .Get("http://localhost:9876/log-json-response")
    .Then()
    .StatusCode(200);
}

For the request, this prints the endpoint, request headers and the request body to the console. Both JSON and XML will be pretty printed.

Other available request log levels are RequestLogLevel.None, RequestLogLevel.Headers, RequestLogLevel.Body and RequestLogLevel.Endpoint.

This prints the response status code, response headers (including cookies), the response body and the response time to the console. Both JSON and XML will be pretty printed.

Other available response log levels are

  • ResponseLogLevel.None - logs no response details
  • ResponseLogLevel.Headers - logs the response status code and the response headers (including cookies)
  • ResponseLogLevel.Body - logs the response status code and the response body
  • ResponseLogLevel.ResponseTime - logs the response status code and the response round-trip time
  • ResponseLogLevel.OnError - logs the same as ResponseLogLevel.All, but only if the response status code is 4xx or 5xx
  • ResponseLogLevel.OnVerificationFailure - logs the same as ResponseLogLevel.All, but only if a verification fails, i.e., when RestAssured.Net throws a ResponseVerificationException

You can also configure the log configuration as part of a RequestSpecification, or globally through the static RestAssuredConfiguration.

Masking sensitive data when logging

If you want to prevent certain sensitive header or cookie values from turning up in the logging, you can pass these as a List<string> property in the LogConfiguration object for the request or response and their value will be replaced with ***** in the logging:

[Test]
public void SingleSensitiveRequestHeaderIsMasked()
{
    var logConfig = new LogConfiguration {
        RequestLogLevel = RequestLogLevel.All,
        ResponseLogLevel = ResponseLogLevel.All,
        SensitiveRequestHeadersAndCookies = new List<string>() { "SensitiveRequestHeader" },
    }

    Given()
        .Log(logConfig)
        .And()
        .Header("NonsensitiveRequestHeader", "This one is printed")
        .Header("SensitiveRequestHeader", "This one is masked")
        .When()
        .Get("http://localhost:9876/masking-sensitive-data")
        .Then()
        .StatusCode(200);
}

You can also specify a list of sensitive request headers and cookies in a RequestSpecification.

For specifying which headers and cookies to mask when logging response details, use the SensitiveResponseHeadersAndCookies property.

Working with server-sent events

In some cases, such as when working with APIs that use server-sent events, it might be useful not to have to wait until the API provider closes the connection. You can tell RestAssured .Net to not wait for this by setting the HttpCompletionOption to ResponseHeadersRead (default is ResponseContentRead):

Given()
    .HttpCompletionOption(HttpCompletionOption.ResponseHeadersRead)

Using a custom HttpClient

In some cases, for example when writing tests for ASP.NET Core Web APIs, you'll need to use the System.Net.Http.HttpClient provided by the WebApplicationFactory. From RestAssured.Net 4.1.0 onwards, you can inject that HttpClient using an optional argument to the Given() method like this:

public void UsingACustomHttpClient()
{
    var webAppFactory = new WebApplicationFactory<Program>();
    var httpClient = webAppFactory.CreateDefaultClient();

    Given(httpClient)
        .When()
        .Get("http://localhost:5154/weatherforecast")
        .Then()
        .StatusCode(HttpStatusCode.OK);
}

The repository contains an example service and corresponding tests to demonstrate the use of this feature.

More background information about this feature can also be found in this blog post.

Logging when using xUnit

By design, xUnit does not automatically print Console.WriteLine() and other print statements to the console. Since RestAssured.Net relies on Console.WriteLine() for logging request and response details, this means that you won't see anything in the console when you're using xUnit.

Here's an example workaround that redirects logging statements to the console when using xUnit:

public class ExampleXunitLoggingTest
{
    private readonly ITestOutputHelper output;

    public ExampleXunitLoggingTest(ITestOutputHelper output)
    {
        this.output = output;
    }

    [Fact]
    public void TestLogging()
    {
        Console.SetOut(new ConsoleWriter(output));

        Given()
            .Log(RequestLogLevel.All)
        .When()
            .Get("https://jsonplaceholder.typicode.com/users/1")
        .Then()
            .Log(ResponseLogLevel.All)
        .And()
            .StatusCode(200)
            .Body("$.name", NHamcrest.Is.EqualTo("Leanne Graham"));
    }
}

public class ConsoleWriter : StringWriter
{
    private ITestOutputHelper output;

    public ConsoleWriter(ITestOutputHelper output)
    {
        this.output = output;
    }

    public override void WriteLine(string? m)
    {
        output.WriteLine(m);
    }
}