Service Models

This topic describes how to work with service models, which describe the behavior of a service. Service models are written using a domain-specific language based on ECMAScript 6. All of the Service Virtualization domain-related language constructs are imported from the lib/sv-vsl.js file, as follows:

import * as sv from "../../../../../../lib/sv-vsl.js"

You can include service models in application scenarios.

How to use Service Models

Every service model extends the ServiceModel class:

export class HelloServiceModel extends sv.ServiceModel {
    ...

The service is passed as a constructor parameter and stored for further use in scenarios. The type of service can be either a custom service interface (see the documentation for the vending-machine demo, available from <installation directory>/Lab/demo) or a generic RestServiceInterface as shown in the following example:

constructor(s: sv.RestServiceInterface) {
    super(s);
    this.hello = s;
}

Alternatively, you can generate a service model in Service Virtualization Designer, as follows:

  1. In Service Virtualization Designer, create a virtualization project.
  2. Create a service model: File > New > Lab > Service Model

Back to top

Service Scenario

Each kind of behavior the service can simulate is described in a scenario. Every scenario is a method annotated with the @scenario annotation, for example:

@sv.scenario
simpleSayHello() {
    ...
}

The scenario contains a sequence of service calls describing the request and response, for example:

this.hello.GET("/hello/{name}")
    .withRequest()
        .withHeaders({"Accept": "application/json"})   
    .withResponse({message : "Hello, world!"}, sv.JSON)         // first response                     
        .withHeaders({"Content-Type": "application/json"})     
        .withStatusCode(200);

this.hello.GET("/hello/{name}")
    .withRequest()
        .withHeaders({"Accept": "application/json"})   
    .withResponse({message : "Hello again, world!"}, sv.JSON)   // second response                     
        .withHeaders({"Content-Type": "application/json"})     
        .withStatusCode(200); 

Every time a scenario like this is simulated, it expects a GET request with a URI parameter (for example, /hello/Martin) and with headers exactly as described in the first call. It returns the first response as a JSON type. Then, it expects a similar request and returns the second response (for example, Hello again, world!).

Back to top

Simulating Stateless Services

Clients may behave differently each time a simulation runs. You can use simulation variables to deal with these situations, for example:

  • To copy data from requests to responses

  • To ignore request headers (since each client may have filled them differently and they are not crucial for our functionality)

Dynamic Responses

During a simulation, client requests may contain some dynamic values varying each time the simulation is run or when the request is sent. The simulation variables allow you to capture such values during the simulation and send them in response. In the example below, one dynamic value is inserted as the URI parameter (the greeting name).

First, you declare the simulation variable and its default value, for example:

let yourName = sv.svVar("world").withValidator(sv.Ignore);

In this example, the variable includes an attached ignore validator. This tells the simulator to ignore the actual value when matching calls from the scenario, enabling the simulator to respond to a request with any name value.

You assign the variable to the request URI parameter and to a property in the response body, for example:

this.service.GET("/hello/{name}")
    .withRequest()
        .withPathParameters({name:yourName}) // URI parameter name bound to the variable yourName
        .withHeaders(...)
    .withResponse({
              "greetings": [
                  "Hello, ",
                  yourName]                  // yourName variable bound to the response
          }, sv.JSON)                        
        .withHeaders(...)      
        .withStatusCode(...);

Ignoring Parts of Message, Headers During Simulation

Different REST clients send different request headers. To build a robust simulation, it is reasonable to ignore the headers in most cases rather than use them to control the simulation. You can do this by instructing the shave ignored the URI parameters: declaring the headers as a simulation variable and assigning it the ignore validator.

return this.service.GET("/hello/{name}")
    .withRequest()
        .withPathParameters(...)
        .withHeaders(sv.svVar({"Accept": "application/json"})   // usual request headers
            .withValidator(sv.Ignore))                          // ignore headers during simulation
    .withResponse(...)                       
        ...

Back to top

Message Templates

You can leverage the ECMAScript language capabilities to declare helper methods instead of repeating code. This enables you to divide a complicated scenario into smaller tasks that are easier to understand and manage.

The following example declares a method with one message that contains pre-populated headers, parameters, and a status code. The example passes the simulation variable, yourName, as a parameter, linking all of the messages in the scenario. The response parameter contains the specific response body:

getHello(yourName, response) {
    return this.service.GET("/hello/{name}")
        .withRequest()
        .withPathParameters({name:yourName}) // URI parameter bound to the scenario variable
        .withHeaders(sv.svVar({"Accept": "application/json"})   // usual request headers
            .withValidator(sv.Ignore))                          // ignore headers during simulation
        .withResponse(response, sv.JSON)                        // the response is always JSON
        .withHeaders({"Content-Type": "application/json"})      // necessary response headers
        .withStatusCode(200);
}

You can call this template function with different data instead of repeating the code:

this.getHello(yourName, {
    "greetings": [
        "Hello, ",
        yourName]
});

this.getHello(yourName, {
    "greetings": [
        "Hello again, ",
        yourName]
});

Declaring the template function so that it returns the pre-populated call object enables you to further customize the call so you can, for example, override the status code for a call in the scenario:

...
this.getHello(yourName, {
    "error": [
        "Oops!"]
}).withStatusCode(500);

Back to top

Learning Service Models

SV Lab supports the learning of REST services. To learn a service behavior:

  • SV lab server must be set up and running.
  • The application under test must be set up to use the virtual endpoint.
  • To generate calls to the service being learned, you must have the test case to run against the application under test.

Examples of learning workflows

This typical workflow shows a test that is invoking an application under test. The application, in turn, calls a third-party service. To capture traffic and learn the service model, the virtual service is inserted between the application and the real service.

In the timezone-api demo, the setup is simplified by omitting the application under test completely and invoking the REST service directly from the test:

Back to top

Learning a REST service

This section describes the process of learning the behavior of a REST service during a test case. Using the SV Capture Tool, you obtain a scenario that is ready for simulation. You can modify the scenario, as needed, and you can use it to compose a complex testing environment with many virtual services.

To learn a REST service:

  1. Launch the sv-capture tool located in the <Lab installation>/bin directory, for example:

    c:\test\Lab\demo\timezone-api>..\..\bin\sv-capture.bat -u https://localhost:8445/api/ -r https://maps.googleapis.com/maps/api/ -p 30000 -d 						./src/test/resources/demo
    
    Operation: GET http://localhost:30000/maps/api//timezone/json
    Service: http://localhost:30000/maps/api/
    Listening...
    Press any key to continue.
  2. In a separate shell, run the test against the application using the service configured to use the virtual service: http://localhost:30000/maps/api

  3. Click Enter to finish capturing.

  4. Check the learned service model—./src/test/resources/demo/SwaggerServiceServiceInterface_Default_Model64.js—which now contains the newly learned scenario:

    @sv.scenario
    capture_SwaggerServiceServiceInterface() {
      ...
    }
  5. Rename the service scenario and use it in the application scenario related to your test case.

Back to top

SV Capture Tool

The sv-capture tool makes it easy to learn REST services by creating a lab with ready-to-use virtual service. The scenario is automatically written into a simulation language source for further use or modification.

SV Capture Tool Options

Option Description
-d,--destdir <arg> Destination directory for the resulting VSL files, for example, /tmp/vslmodel.

-h,--help

Show Help.
    --log-level <arg>

Log level.

One of TRACE, ALL, ERROR, INFO (default), FATAL, DEBUG, OFF, or WARN.

-m,--service-mode <arg>

Service mode.

One of FORWARD (default), SIMULATE, or INVOKE.

-p,--capture-port <arg> TCP port for the start of the capture endpoint of the virtual service.
-r,--real-service <arg> Real REST service to which the traffic is forwarded.
-s,--swagger <arg> REST service swagger.json interface.
-u,--sv-lab-url <arg> SV Lab API endpoint URL to connect to.
-v,--verbose Show more information about the progress.
-vsl,--vsl-path <arg> Path to REST service VSL .js model scripts.

Back to top