keploy icon indicating copy to clipboard operation
keploy copied to clipboard

[GSoC]: test multiple services with keploy

Open gouravkrosx opened this issue 1 year ago • 5 comments

Is there an existing feature request for this?

  • [X] I have searched the existing issues

Summary

  • Use keploy to record tests and mocks for multiple interconnected services.
  • And observe the changes in keploy's behavior if there is a major update in one of the services and its impact on new test environments.

Why should this be worked on?

  • Since Most of the applications consist of multiple microservices, keploy should be able to test all the services together.

Repository

keploy

gouravkrosx avatar Feb 09 '24 06:02 gouravkrosx

Use keploy to record tests and mocks for multiple interconnected services:

I made two spring boot interconnected microservices which are product and order

Models:

Order

```
package com.example.order.models;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class Order {
    private Long id;
    private Long productId;
    private int quantity;

}
```

Product

```
package com.example.order.models;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class Product {
    private Long id;
    private String name;
    private double price;

}
```

Order Controller:

```
package com.example.order.controllers;
import com.example.order.models.Order;
import com.example.order.models.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/orders")
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping
    public ResponseEntity placeOrder(@RequestBody Order order) {

        ResponseEntity responseEntity = restTemplate.getForEntity(
                "http://localhost:8084/products/{productId}", Product.class, order.getProductId());

        if (responseEntity.getStatusCode() == HttpStatus.OK) {
            Product product = responseEntity.getBody();
            return ResponseEntity.ok("Order placed for product: " + product.getName() +
                    ", Quantity: " + order.getQuantity() +
                    ", Total Price: " + (product.getPrice() * order.getQuantity()));
        } else {
            return ResponseEntity.status(responseEntity.getStatusCode()).body("Failed to place order");
        }
    }
}
```

Product Controller:

```
package com.example.product.controllers;


import com.example.product.models.*;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/products")
public class ProductController {

    @GetMapping("/{productId}")
    public ResponseEntity getProduct(@PathVariable Long productId) {
        // In a real scenario, you might fetch product details from a database
        Product product = new Product();
        product.setId(productId);
        product.setName("Sample Product");
        product.setPrice(19.99);

        return ResponseEntity.ok(product);
    }
}
```

Sending order request using curl:

```
curl --location 'http://localhost:8085/orders' \
--header 'Content-Type: application/json' \
--data '{
    "id":101,
    "productId":105,
    "quantity":5
}'
```

While recording the request of order microservice by Keploy:

image

Test case is generated successfully:

```
version: api.keploy.io/v1beta1
kind: Http
name: test-1
spec:
    metadata: {}
    req:
        method: POST
        proto_major: 1
        proto_minor: 1
        url: http://localhost:8085/orders
        header:
            Accept: '*/*'
            Content-Length: "55"
            Content-Type: application/json
            Host: localhost:8085
            User-Agent: curl/7.68.0
        body: |-
            {
                "id":101,
                "productId":105,
                "quantity":5
            }
        body_type: ""
        timestamp: 2024-02-14T11:14:59.875844811+02:00
        host: ""
    resp:
        status_code: 200
        header:
            Content-Length: "85"
            Content-Type: text/plain;charset=UTF-8
            Date: Wed, 14 Feb 2024 09:15:00 GMT
        body: 'Order placed for product: Sample Product, Quantity: 5, Total Price: 99.94999999999999'
        body_type: ""
        status_message: ""
        proto_major: 0
        proto_minor: 0
        timestamp: 2024-02-14T11:15:02.872504633+02:00
    objects: []
    assertions:
        noise:
            header.Date: []
    created: 1707902102
curl: |-
    curl --request POST \
      --url http://localhost:8085/orders \
      --header 'Host: localhost:8085' \
      --header 'User-Agent: curl/7.68.0' \
      --header 'Accept: */*' \
      --header 'Content-Type: application/json' \
      --data '{
        "id":101,
        "productId":105,
        "quantity":5
    }'
```

image

AhmedLotfy02 avatar Feb 14 '24 09:02 AhmedLotfy02

Can you explain more about "a major update in one of the services"?

AhmedLotfy02 avatar Feb 14 '24 09:02 AhmedLotfy02

@AhmedLotfy02 How did you start the services using keploy? Because I can see only one test case being generated but it should have been 2, one for each service.

Can you explain more about "a major update in one of the services"?

By this, I mean that if there is some change in the API of the second service, how will it affect the other service?

gouravkrosx avatar Feb 14 '24 09:02 gouravkrosx

@gouravkrosx I started order service by keploy and the second one normally. but now when i started the second one with keploy one test case is generated for the second service and one for the first service also after requesting the order controller. (two different terminals)

in the first service: the first test case in this screenshot is related to the above test ignore it) image in the second service: image

AhmedLotfy02 avatar Feb 14 '24 10:02 AhmedLotfy02

ok according to

By this, I mean that if there is some change in the API of the second service, how will it affect the other service?

if I understood what you mean making a major changes in the second service which is product service like returning a string rather than product it will cause an error as it expecting that product service will return a product: image the return from order service: image

also Keploy will record these test cases: test case generated in order service: image test case generated in product service: image

AhmedLotfy02 avatar Feb 14 '24 10:02 AhmedLotfy02

@AhmedLotfy02 ,

By this, I mean that if there is some change in the API of the second service, how will it affect the other service?

I meant after recording a few test cases, if you make some changes in the Product service & run both the services in test mode, what will be the behavior? for both services. Do you think the behavior is valid or not?

gouravkrosx avatar Feb 25 '24 19:02 gouravkrosx

I added the code with the PR linked to this issue

After recording a testcase then change one field of the returned object in product service then record then test the both services:

  1. Order Service's tests failed due to this breaking change which is valid:
    image
    image
  2. Product service didn't fail also returned response is changed which is invalid behaviour:
    image

AhmedLotfy02 avatar Mar 03 '24 21:03 AhmedLotfy02

@AhmedLotfy02 This is the actual problem I wanted to see even if there is some change in one service (s2), it is not reflected for the other service(s1) which should not be the case here. Let's say the change in other service(s2) is legit then you'll just update the test case. But the problem is that the mocks will not be updated for the other service(s1) which can break the application or you can say contract.

So, The main GSOC issue is that keploy should be able to support this that mocks should be updated every time there is such change in a different service.

gouravkrosx avatar Mar 04 '24 06:03 gouravkrosx

Since the test fail to capture the fact that a consumer service is using an outdating mocks right now, to make things work, when making changes (changing the contract) on a service, we have to update tests file in the provider service and the mocks file in the consumer service, which requires too much manual effort. Adding a contract that allows access for both the provider and the consumer might solve this problem. It can acts as stubs for consumer and normal test cases for provider, which ensure synchronization. I am thinking of where to store these shared contract... It should not be in codebase of microservices or in the cloud.

hanzili avatar Mar 18 '24 07:03 hanzili

@gouravkrosx I've implemented it. Please take a look when you have time. https://github.com/seipan/multiple-services-go

seipan avatar Apr 01 '24 11:04 seipan

This was related to the GSoC qualification task, since the proposal period has been closed, I am closing the issue. Thanks everyone for the contribution.

gouravkrosx avatar Apr 03 '24 06:04 gouravkrosx