0% found this document useful (0 votes)
8 views15 pages

Serialization Deserialization CleanCode RestAssured Part2

The document provides an in-depth guide on serialization and deserialization using Rest Assured and Jackson in Java, emphasizing clean coding principles such as SOLID and DRY. It includes practical examples of POJOs, API request/response handling, and real-time scenarios to illustrate the importance of type safety and maintainability in API automation. Additionally, it features a comprehensive Q&A section addressing common challenges and best practices in API testing and coding standards.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views15 pages

Serialization Deserialization CleanCode RestAssured Part2

The document provides an in-depth guide on serialization and deserialization using Rest Assured and Jackson in Java, emphasizing clean coding principles such as SOLID and DRY. It includes practical examples of POJOs, API request/response handling, and real-time scenarios to illustrate the importance of type safety and maintainability in API automation. Additionally, it features a comprehensive Q&A section addressing common challenges and best practices in API testing and coding standards.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

Serialization, Deserialization & Clean

Coding Principles – With Rest Assured


(Java)
Prepared for: Prabhu Dhandapani | Role: Java Automation Testing Lead | Focus: Rest Assured
+ Clean Code

Part 1: Serialization & Deserialization (Rest Assured + Jackson)


Definition:

• Serialization: Converting Java objects into JSON (or XML) for outbound HTTP requests.

• Deserialization: Converting JSON (or XML) responses into Java objects for type-safe
assertions.

Why it matters (lead-level):

• Type-safety, compile-time checks, cleaner assertions, schema evolution safety with


@JsonIgnoreProperties.

• Enables builders/test data factories instead of ad-hoc Maps/Strings, reducing duplication


and bugs.

POJO with Jackson annotations

package models;

import [Link];

import [Link];

import [Link];

@JsonIgnoreProperties(ignoreUnknown = true)

@JsonInclude([Link].NON_NULL)

public class Employee {

@JsonProperty("id")
private Integer id;

@JsonProperty("name")

private String name;

@JsonProperty("role")

private String role;

@JsonProperty("email")

private String email;

public Employee() {}

public Employee(String name, String role) { [Link] = name; [Link]


= role; }

public Integer getId() { return id; }

public void setId(Integer id) { [Link] = id; }

public String getName() { return name; }

public void setName(String name) { [Link] = name; }

public String getRole() { return role; }

public void setRole(String role) { [Link] = role; }

public String getEmail() { return email; }

public void setEmail(String email) { [Link] = email; }

Serialization – POST body from POJO

Employee emp = new Employee("Prabhu-Auto", "QA Lead");

[Link]("[Link]@[Link]");

Response res = given()


.baseUri([Link]("baseUri", "[Link]

.contentType([Link])

.body(emp)

.when()

.post("/employees")

.then()

.statusCode(201)

.extract().response();

Deserialization – Response to POJO

Employee created = [Link]([Link]);

assertNotNull([Link]());

assertEquals("QA Lead", [Link]());

Nested & List deserialization (jsonPath)

List<Employee> employees = given()

.baseUri(baseUri)

.when()

.get("/employees")

.then()

.statusCode(200)

.extract().jsonPath().getList("items", [Link]);

assertTrue([Link]() > 0);


Real-time scenarios (Serialization/Deserialization)
1) Contract evolves with new optional fields: keep tests stable with @JsonIgnoreProperties
and builders.

2) Negative model case: Deserialize error payload into ErrorResponse POJO and assert
errorCode/message/correlationId.

3) Large payloads: Stream JSON where possible; avoid building massive strings; use POJOs
and Jackson for efficiency.

Part 2: Clean Coding Principles (SOLID + DRY + Simplicity) for API


Automation

Single Responsibility Principle (SRP)


Definition:

A class or method should have only one reason to change.

Why it matters:

Reduces coupling and cognitive load. Easier to maintain and test.

Code example:

// BAD: mixes config, auth, request building, and tests

class ApiUtil { /* getToken(), buildSpec(), postEmployee(), writeExcel() */


}

// GOOD: separate responsibilities

class ConfigManager { /* reads config/system props */ }

class RequestFactory { /* builds base RequestSpecification */ }

interface AuthStrategy { RequestSpecification apply(RequestSpecification


spec); }

class BearerTokenStrategy implements AuthStrategy { /* adds Authorization


header */ }

class ApiClient { /* get/post/put/delete using strategy + factory */ }


Real-time scenario:

When auth or logging changes, only the relevant class changes; tests remain untouched.

Open/Closed Principle (OCP)


Definition:

Open for extension, closed for modification.

Why it matters:

Add new features (e.g., auth types) without breaking stable code.

Code example:

public interface AuthStrategy { RequestSpecification


apply(RequestSpecification spec); }

public class BasicAuthStrategy implements AuthStrategy { /* preemptive


basic */ }

public class BearerTokenStrategy implements AuthStrategy { /* bearer token


*/ }

// Later add

public class ApiKeyStrategy implements AuthStrategy { /* x-api-key header


*/ }

// ApiClient never changes; we just inject new strategy

Real-time scenario:

A project adds API-Key auth for a partner. Add ApiKeyStrategy and wire via config—no core
refactor.

Liskov Substitution Principle (LSP)


Definition:

Subtypes must be substitutable for their base types.

Why it matters:
Allows polymorphic use of clients/strategies in tests.

Code example:

AuthStrategy strategy = new BearerTokenStrategy();

// Later swap

strategy = new BasicAuthStrategy();

ApiClient client = new ApiClient(strategy); // works without changing tests

Real-time scenario:

Switch auth per environment (QA uses basic, UAT uses bearer) without changing test code.

Interface Segregation Principle (ISP)


Definition:

Prefer many small, client-specific interfaces over one large interface.

Why it matters:

Prevents implementing unused methods; clearer contracts.

Code example:

interface GetOperation { Response get(String path); }

interface PostOperation { Response post(String path, Object body); }

// Clients implement only what they need

Real-time scenario:

A read-only service implements only GetOperation; write endpoints use PostOperation—no


empty stubs.

Dependency Inversion Principle (DIP)


Definition:
High-level modules depend on abstractions, not concrete classes.

Why it matters:

Swap implementations (real/mocked) without changing dependents. Improves testability.

Code example:

public class Tests {

private final ApiClient api;

public Tests(ApiClient api){ [Link] = api; }

// In unit tests: inject a FakeApiClient; in E2E: inject RestAssured-backed


ApiClient

Real-time scenario:

Local runs use a mock/fake; CI runs hit QA env. Same tests, different wiring.

DRY (Don't Repeat Yourself)


Definition:

Avoid duplicating logic and configuration.

Why it matters:

Centralized changes, fewer defects.

Code example:

class RequestFactory {

static RequestSpecification base(){

return new RequestSpecBuilder()

.setBaseUri([Link]("baseUri"))

.setContentType([Link])

.log([Link])
.build();

// Reuse [Link]() across all tests

Real-time scenario:

Base URI, headers, and logging are changed in one place; hundreds of tests pick it
automatically.

Reduction of Complexity
Definition:

Prefer small, readable methods; early returns; minimal branching.

Why it matters:

Improves readability and reduces bugs.

Code example:

public Response findEmployee(int id){

return given().spec([Link]())

.pathParam("id", id)

.when().get("/employees/{id}")

.then().extract().response();

Real-time scenario:

Debugging failures is faster when each method has a clear, single purpose and minimal
branching.
Elimination of Duplicate Code
Definition:

Abstract common patterns into helpers/utilities.

Why it matters:

Consistency and fewer maintenance points.

Code example:

public final class ResponseAssert {

public static void assertCreated(Response r){

assertEquals(201, [Link]());

assertNotNull([Link]().get("id"));

// Reuse in all create tests

Real-time scenario:

Standardized checks for create/update/delete reduce test verbosity and mistakes.

Grouping by Functionality
Definition:

Organize packages by feature/domain (users, orders) rather than only by layer.

Why it matters:

Feature ownership and quicker navigation.

Code example:

/employee

[Link]
[Link]

[Link]

[Link]

Real-time scenario:

When a feature changes, you open one folder and see client, tests, schema, and data
together.

Code Reusability
Definition:

Design components (builders, clients, assertions) to be reused across tests/projects.

Why it matters:

Accelerates new suites and keeps quality consistent.

Code example:

public class Retry { /* executeWithRetry(Supplier<Response> ...) */ }

public class TokenProvider { /* get/refresh token */ }

public class JsonUtil { /* toJson/fromJson helpers */ }

Real-time scenario:

New microservice onboarding uses the same core utilities and patterns—only endpoints
and models differ.

Part 3: Interview Q&A (EPAM Style) – Serialization/Deserialization + Clean


Code
Q: What is the advantage of POJOs over Maps for API payloads?

A: POJOs give type-safety, IDE support, and safer evolution with Jackson annotations; Maps
are quick but brittle and error-prone.
Q: How do you keep tests stable when API adds new fields?

A: Use @JsonIgnoreProperties(ignoreUnknown = true) and avoid strict field-by-field equals


unless needed; prefer schema validation for structure.

Q: How do you validate both contract and business rules?

A: Use JSON Schema for contract (shape/types) and AssertJ/Hamcrest for business
fields/values. Separate concerns keeps tests clean.

Q: Explain a time you reduced duplication in API tests.

A: Centralized base RequestSpecification and common assertions; reduced 300+ lines


across suites and made logging consistent.

Q: How do you introduce a new auth mechanism without refactoring tests?

A: OCP + DIP: add a new AuthStrategy implementation and inject via config; ApiClient and
tests stay unchanged.

Q: How do you handle flaky 5xx and 429 in a clean way?

A: Create a reusable Retry utility with exponential backoff, use idempotency keys for POSTs,
and isolate flaky endpoints into a quarantine group.

Q: How do you ensure readability of tests for non-engineering stakeholders?

A: Use BDD (Cucumber) for high-level scenarios and keep step defs thin; push HTTP details
into clients/builders.

Q: How do you structure features in the repo?

A: Group by functionality (employee/, order/) with client, tests, schema, and data together
for quick changes and ownership.

Part 3 (Expanded): EPAM-Style Interview Q&A –


Serialization/Deserialization + Clean Code
Below are 24 additional scenario-driven Q&A (total Part 3 items: 32). They go deeper into
Jackson configuration, polymorphic types, dates, enums, error handling, builders, retry,
token refresh, contract testing, and code quality practices.

Q1: How do you handle nulls during serialization to avoid sending empty fields?

A: Use Jackson @JsonInclude([Link].NON_NULL) at class or ObjectMapper


level to exclude nulls and reduce payload noise.

Q2: Your API returns extra fields not in your POJO. How to keep tests stable?
A: Annotate POJO with @JsonIgnoreProperties(ignoreUnknown = true). This allows
forward-compatible deserialization while you evolve models.

Q3: When would you prefer Maps over POJOs for request bodies?

A: For quick spike tests or dynamic ad-hoc payloads. For maintainable suites, prefer
POJOs/builders for type-safety and reuse.

Q4: How to serialize dates consistently (e.g., ISO-8601) so server accepts them?

A: Configure ObjectMapper with JavaTimeModule and write dates as ISO strings, or ensure
string fields carry RFC3339/ISO format from builders.

Q5: Enum fields failing due to case differences—what’s your approach?

A: Use @JsonProperty on enum constants or a custom deserializer to map input values case-
insensitively; validate against allowed values list.

Q6: Polymorphic JSON (type field decides subclass). How to deserialize?

A: Use @JsonTypeInfo(use = [Link], property = "type") and @JsonSubTypes on the base


class. Keep tests per subtype to validate business rules.

Q7: How do you deserialize only a part of a large response for performance?

A: Use JsonPath to extract just the needed fragment or create a slim POJO that models only
the required subset.

Q8: How do you validate that serialization respects API contract?

A: Combine schema validation with a golden-sample payload built via POJO. Serialize the
POJO and validate it against the JSON schema before sending.

Q9: Object contains computed/derived fields you do not want to send—what to do?

A: Mark with @JsonIgnore on getters/fields or build separate request/response DTOs to


keep concerns clean.

Q10: Your response contains BigDecimal money values—precision issues?

A: Use BigDecimal in POJOs, not double. Assert using string comparison or compareTo(0)
for ranges to avoid floating precision errors.

Q11: The API returns gzip/compressed responses—any change to deserialization?

A: Rest Assured handles it transparently via content-encoding. Ensure Accept-Encoding is


set (usually default) and assert decompressed body fields.

Q12: How do you map snake_case JSON to camelCase POJO fields?


A: Configure ObjectMapper with PropertyNamingStrategies.SNAKE_CASE or annotate with
@JsonProperty to map differing names.

Q13: What’s your strategy for error responses?

A: Create ErrorResponse POJO (code, message, traceId, details[]). On non-2xx, deserialize


into ErrorResponse and assert consistent error contract.

Q14: How do you avoid repeating serialization boilerplate in tests?

A: Centralize in DataFactory/Builder classes that return ready-to-send POJOs; keep


variations as builder methods (withEmail, withRole, invalidEmail).

Q15: Explain a clean approach to token refresh impacting serialization.

A: Keep token logic in TokenProvider; RequestFactory/AuthStrategy uses it. Serialization of


bodies stays independent, respecting SRP.

Q16: How do you trace serialization problems quickly?

A: Log the outbound JSON (with secrets redacted) and validate against schema. Add Allure
attachments with pretty-printed payloads.

Q17: When do you use custom serializers/deserializers?

A: When external format differs (e.g., epoch millis, custom money type). Implement
JsonSerializer/JsonDeserializer and register with ObjectMapper.

Q18: How to assert that deserialization didn’t lose data?

A: Round-trip test: deserialize -> serialize back -> compare significant fields or validate
against schema; ensure enums/dates preserved.

Q19: Handling partial updates (PATCH) using POJOs?

A: Use builders producing minimal POJOs containing only changed fields; ensure nulls are
excluded via @JsonInclude to avoid unintended overwrites.

Q20: What if the same resource has different shapes in v1 vs v2?

A: Version POJOs (EmployeeV1, EmployeeV2) or keep one with optional fields and feature
flags. Run contract validation per version.

Q21: How do you keep serialization logic reusable across services?

A: Publish common DTOs/util libraries (internal Maven artifact) and share


JsonUtil/ObjectMapper configuration via a core module.

Q22: Testing locale-specific formats (numbers/dates)?


A: Send/expect ISO formats in APIs; for locale-sensitive fields, include Accept-Language
headers and assert formatting where business requires.

Q23: How do you guard against breaking changes introduced by refactoring POJOs?

A: Write unit tests for (de)serialization using sample JSON fixtures; run contract/schema
validation in CI on every PR.

Q24: You need to compare responses from two environments for drift—best way?

A: Deserialize both to POJOs, ignore volatile fields (timestamps/ids), and compare using
AssertJ’s usingRecursiveComparison with field filters.

Code Mini-Snippets (for expanded Q&A)


Exclude nulls while serializing:

ObjectMapper mapper = new ObjectMapper()

.setSerializationInclusion([Link].NON_NULL);

Snake_case → camelCase mapping:

ObjectMapper mapper = new ObjectMapper()

.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);

Register JavaTimeModule for dates:

ObjectMapper mapper = new ObjectMapper();

[Link](new JavaTimeModule());

[Link](SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

Custom enum mapping:

enum Status {

@JsonProperty("in_progress") IN_PROGRESS,

@JsonProperty("done") DONE;

Polymorphic type setup:


@JsonTypeInfo(use = [Link], property = "type")

@JsonSubTypes({ @Type(value = [Link], name = "admin"),

@Type(value = [Link], name = "user") })

abstract class User { }

AssertJ recursive comparison ignoring fields:

assertThat(actual).usingRecursiveComparison()

.ignoringFields("id", "timestamp")

.isEqualTo(expected);

You might also like