TEST ENVIRONMENTSAn Introduction to CodeSV

How to get Started with CodeSV: Developer Friendly Service Virtualization
Petr Vlasek Petr VlasekSeptember 8, 201913 min

In this article, What is Service Virtualization, and How Can It Benefit Developers I discussed how beneficial is to use concepts of service virtualization during development and how it is critical to enable shift-left and Continuous Testing practices. However, one thing is to be aware of the benefits, the other thing is to get all of that into the daily practice within your development teams. In this blog post we would like to focus on how development teams can seamlessly leverage the concept of service virtualization and integration it with Blazemeter and its Mock Services capability there.

Developer Friendly Testing Tools

Developers can be very picky about the tools they want to use. Typically, tools that developers like to use on a daily basis are oriented to stuff that could be expressed via code performed from IDE, or command-line, or accessed via some API. (I speak as someone with more than 10 years in software engineering and architect roles)

Developers are also affected by external factors and constraints – with one of the strongest factors being the architecture of the application that developers are dealing with. If it is a legacy monolith, then the toolchain that the dev team is working on in that project would strongly differ from the team that is dealing with modern and recent stuff focused on microservices, containers, Kubernetes and so on. 

Another important factor which influences developers and the tools they use are organizational policies that may define alignment of dev teams around a specific toolchain, tech stack and process – I can simply call it as technical culture of the organization.

Putting all of the above aspects together – i.e. from individual preferences to options enforced by organizational policies – you can ask 10 different developers and you may get 10 different responses based on preferences for different tools, depending on what they previously used, or based on their specific function within the organisation, etc. 

Service Virtualization Tools for Developers

That is why we have focused our Service Virtualization (SV) “for developers” efforts around a variety of choices. These include CodeSV to SV-as-Code libraries, APIs to command-line interfaces or IDE plug-ins, and Jenkins pipelines to Docker containers. We want to give developers a choice of tools to fit their needs and also their individual preferences. In this blog post, I will focus on “the purest developer-friendly” SV tool we have in our portfolio – CodeSV.

What is CodeSV?

CodeSV is a regular Java library that could be added to your project as a Maven or Gradle dependency (or as a plain .jar file library if you would prefer this way). It provides two key functionalities:

  • Fluent API to easily define your virtual service

CodeSV API is designed in a way that helps you to define your virtual services in code as you type. By using the Builder pattern, you can effectively leverage the IDE power of code-completion to define your virtual service in seconds, and without the need to check documentation after every single method. Everything is self-explanatory, and in addition, the IDE code completion provides auto suggestion capabilities. 

I will demonstrate this with an example below. Let’s build up a very simple virtual service and explain what I am doing.

First I have to declare what type of request I want to return a virtualized response. In this case, I want to virtualize the response of a GET HTTP request. Clearly, following the same pattern, I can do the same with forPost() for POST request, etc.

forGet();

Next, a GET HTTP request pointing to where. As you can see below, I am interested in GETs coming to myhost.com AOI endpoint for user accounts.

forGet(“myhost.com/api/users”);

Now I have defined what I am interested in, or what to match, but what about the response?

forGet(“myhost.com/api/users”).doReturn();

Ok, this looks promising – from matching of the request I have moved my attention to what I want to return. But what am I going to return?

forGet(“myhost.com/api/users”).doReturn(okMessage());

It is an HTTP 200 message. Would you like to add any text content there?

forGet(“myhost.com/api/users”).doReturn(okMessage().withStringBody(“Hello from virtual service);

And that’s it!  For any GET HTTP request coming to myhost.com/api/users I am going to return HTTP 200 with “Hello from virtual service” response. Our first virtual service is here – it is as simple as that. With all of the help from IDE and auto-completion features I can define this in less than 10 seconds (and believe me, I am not a fast typer at all). Putting this to my test code, anytime my application triggers GET to myhost.com/api/users the virtualized response is returned.

Ok, this example was easy and somewhat artificial. But keep reading, we are going to virtualize something real.

  • In-process virtualization engine

CodeSV implementation uses an approach we call “in-process” virtualization. This way CodeSV can virtualize responses over-the-wire without the need to reconfigure application your are testing to point to any special CodeSV endpoint for virtual responses. You just define what you want to virtualize, run your test and everything happens automatically. It does not matter whether you run your test from your IDE or whether the test is checked in to repository and being executed by some CI pipeline.

Our first virtualization exercise

Right, let’s take a look at a real use-case. But before we will deep dive into the code, let me first introduce what application we are going to implement and test.

Getting familiar with the  demo application

Let’s imagine we are developing a slick web banking application called Digital Bank. Such an application will be built around various (micro-)services but it will also have some external 3rd party dependencies. In our case, I would like my Digital Bank application to provide ATM search functionality.

Note: The good news is that this Digital Bank demo application is open-source and is available for your experiments so you can follow this blog post and try all of the actions by yourself – feel free to check its Java source code https://github.com/asburymr/Digital-Bank and Docker distribution: https://hub.docker.com/r/asburymr/digitalbank. You can follow this blog post and try all of the actions by yourself.

In the screenshots below, you can see the home screen of the Digital Bank application and the ATM search functionality available there.

codeSV

After clicking on magnifier icon on the top, I can enter zip of area I am interested in…

getting started with codesv

And I see available ATMs in this area…

code sv for developers

The little secret is that the ATM search functionality in our Digital Bank application is using 3rd party service instead using anything developed from scratch. In our example we use synapsefi.com – it searches for the ATMs within a radius of the area provided by the zip code. My application just sends request to this 3rd party API, gets results in response and displays them nicely in its user interface.

The ATM search API is pretty easy to be used – it runs over https and with query parameter zip I specify in which area I am interested in and I get the available ATMs in response. All of that I did here (feel free to use any API client, in my case it is a command-line wget) – the following command stores response into file with JSON content:

$ wget https://uat-api.synapsefi.com/v3.1/nodes/atms?zip=94203

{
  "atms": [
    {
      "atmLocation": {
        "address": {
          "city": "San Francisco",
          "country": "USA",
          "postalCode": "94114",
          "state": "CA",
          "street": "443 Castro Street"
        },
        "coordinates": {
          "latitude": 37.761746,
          "longitude": -122.435024
        },
        "id": "818850",
        "isAvailable24Hours": true,
        "isDepositAvailable": true,
        "isHandicappedAccessible": false,
        "isOffPremise": false,
        "isSeasonal": false,
        "languageType": null,
        "locationDescription": "US BANK CASTRO",
        "logoName": "PAS",
        "name": "U.S. Bank Castro"
      },
      "distance": 0.4101105159902258
    },
    {
      "atmLocation":
...
    }
  ],
  "atms_count": 4,
  "error_code": "0",
  "http_code": "200",
  "limit": 20,
  "page": 1,
  "page_count": 1,
  "success": true
}

But back to our Digital Bank application. Its architecture is pretty simple. It is a Spring Boot web application with Javascript front-end. For our needs it is enough to know that there is SearchService class in io.demo.bank.service package and instance of this class is responsible for the ATM search functionality – it builds a HTTP request to the 3rd party API, gets back the response, converts it to domain objects (list of AtmLocation entities) and return to the caller.

How to deal with dependencies on 3rd party service during development and testing?

The setup described above is great for what we would like to showcase as the value of service virtualization. We have our application that depends on another service, in this case on a 3rd party service that is out of our control. What is more – we are not only showing data coming from the 3rd party service – we are doing conversions between formats (from JSON to our domain model), and also our application shows it in our tailored UI with icons, status/error messages and so on.

Now, let’s assess our options for integration testing of our application and the Synapsefi API which is behind our ATM search functionality. We could hard-code the zip code and do the assertions to define expected test results we are getting from the real service. However, how can we  make our test stable if we do integrate with an API that returns data that may change over time? In our case the data are not subject to be changed every minute or even second, like in the case of for example weather forecast, stock exchange or traffic information,… – but still it can change as the new ATMs are being added or moved to different locations. In our test we would like to use some immutable case and not rely on results that may change. 

Also, we are getting a “correct” response – Synapsefi API just works and returns data we would expect for a particular location. But what about figuring out how our application would behave if it will encounter some corner or edge case data – does it handle them properly or would these cases break the UI? What if the response from the 3rd party API is timing out or taking too long to respond due to network issues? Will our application recover and try it again, or just show some cryptic timeout exception in the UI? What if the 3rd party API will not be available temporarily – will our app fail gracefully or will it freeze? Will it also paralyze our testing? (Most likely it will…). And what if the 3rd party API will return some empty data because their data provider cannot deliver them – could our app recover and display some user-friendly error? There are many “what ifs” and remember we are just talking about a very simple use-case. 

In theory, we can create some hard-coded mock implementation (that will just cover all “what ifs” cases from above) but this takes time, does not scale and it is simply hardcoded for a single service and single use-case. And we have to realize we are talking about very simple scenario here – imagine that our app has more dependencies that could be much more complex.

The solution to these issues is to use the service virtualization approach that helps to easily describe how the dependency should be virtualized, and do it in a flexible, reusable and scalable way. This way we are able to define tests that will really prove whether our integration with 3rd API is ready for any eventual case that may happen.

Time for action with CodeSV

Let’s wrap up our intro blog post with our very first definition of a virtual service for Synapsefi API. We will define a simple test that will test the integration between our application and the 3rd party API. This way we will get a robust test that ensures our application correctly interprets ATM location response and results.

In the AtmLocationSearchTest class (io.demo.bank.test.junit.search package) let’s define positiveResultTest() test.

@Test
public void positiveResultTest() throws Exception {
   List<AtmLocation> results = searchService.searchATMLocations("94203");
   assertTrue(results.size() == 2);
}

This test initiates the ATM search flow for the 94203 zip code. It retrieves results from Synapsefi API. It then asserts whether the number of ATMs is equal to the expected number. This test is fine, except the fact that Synapsefi API could return various results over the time.

To fix this, we can virtualize Synapsefi API directly in the test method using CodeSV, as you can see below. First, we will add the following lines to the beginning of the test.

forGet("https://uat-api.synapsefi.com:443/v3.1/nodes/atms")
       .matchesQuery("zip", "94203")
       .usingHttps(
               withSecureProtocol("TLS")
                       .keystorePath(KEYSTORE_PATH)
                       .keystorePassword(KEYSTORE_PASSWORD)
                       .keyPassword(KEYSTORE_PASSWORD)
       )
       .doReturn(okMessage().withJsonBody(POSITIVE_RESULTS_OK));

There is a definition of the virtual service written directly in the code. It defines that for any GET HTTP request pointing to Synapsefi path that the test is going to hit, the service is going to return OK response with JSON body containing the response string literal defined in POSITIVE_RESULTS_OK format. You may notice usingHttps() method that indicates the virtual service should support https connections.

Now, anytime when this test runs, it will hit the virtual service provisioned in process (note the test, or the application respectively, still believes it is connecting to the real Synapsefi service). The virtual response returns 2 ATMs found – the same as our assertion is expecting. Therefore, we can test whether our application implementation, and especially response handling of Synapsefi response, is correctly implemented.

To try out CodeSV for yourself click here, or watch this online video.

Spread the love
Petr Vlasek

Petr Vlasek

Petr works as Product Owner at Broadcom with focus on Continuous Testing products and solutions. In past he worked in architect and product roles in areas of workload automation, application security and blockchain research. His current passion is finding of innovative ways how to take Continuous Testing solutions to the next level and make them developer-friendly.

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts