Table of Contents |
---|
Purpose
This page contains uniform rules for Eureka development
Helpful files
Checkstyle
View file name eureka_checkstyle (1).xml Intellij Idea
View file name eureka_codestyle (1).xml
General
Sonarcloud rules are important
If the current version of SonarCloud does not support any functionality, use a suitable implementation
For example, instead of using.toList()
- use.collect(toList())
or.collect(toUnmodifiableList())
for an immutable list.If sonar provides incorrect validation, it must be discussed with a team and suppressed with a comment
Methods
Length
Method length must be less than 25 lines in most cases, excluding the declaration line and closing brace. If implementation takes more lines - try to decomposite method/class.
Naming
Each method must start with a verb in imperative form:
startProcessing
,doOperation
,performAction
,mapResources
The return value is boolean: is, exists, has. However, a noun can go first if it simplifies the method name:
usernameExists
orexistsByUsername
If the method is aimed to validate and then throw a validation exception, use verbs:
verify
,check
,assert
onCreate
,onUpdate
are valid names for listener methods
Methods/operation usage rules
Use
isNull()
/isNotNull()
methods in as a method reference in lambdas, otherwise!= null
or== null
is more clear
...
Don’t modify source data to perform an action, it can be used further and those changes can lose initial information from the event/read operation.
Prefer a more functional approach when the method always returns a new value
BeanUtils.copyProperties()
, collections are copied by reference,deepCopy
mode if it is required to copy collection fields tooThe method can return modified value - if it is used for model construction (so-called
with
methods, or chain setters)
Check utility method from
folio-backend-tools
Logging
Log level = TRACE
No need to provide logs for this level
...
Code Block |
---|
"{message}: {parameter1} = {value1}, {parameter2} = {value2}" // examples log.info("Operation execution finished: id = {}", UUID.randomId()) // there is no need to manyally specify tenant name, because it will be populated by Folio logger extension from values from FolioExecutionContext log.info("Tenant is initialized") // if serialization of value is not a straight-forward, use a consumer notation log.debug("Value is created: value = {}", () -> asJsonString(someValue)) |
Unit tests
Unit tests must satisfy F.I.R.S.T principles.
Naming
Code Block | ||
---|---|---|
| ||
{methodName}_{returnType:positive|negative}_{condition:inputValueIsNull} |
...
Tests (creating stubs): createApplicationDescriptor() → applicationDescriptor()
Unit tests can be structured with a
@Nested
class for each method, however it is not mandatoryUnit tests must use AssertJ library for uniformity, other assertion tools are less readable, but can be faster, however, there are no problems for big services using assertj
A reflection comparison can be used to ignore specific fields for model or the models without implemented
equals
methodcontainsExactly
,containsExactrly
in any order to check collection elements of retrieving them one by onehttp://joel-costigliola.github.io/assertj/ - check other comparisons
Exception validation
Make sure that all significant exception parameters are verified.
...
Code Block | ||
---|---|---|
| ||
@Test void validateEntity_positive() { var expectedParameters = List.of( new Parameter().key("request.key").value("must not be null"); ); assertThatThrownBy(() -> service.validateEntity(request)) .isInstanceOf(RequestValidationException.class) .hasMessage("Found validation errors in request") .satisfies(err -> assertThat(((RequestValidationException) err).getErrorParameters()).isEqualTo(expectedParams)); } |
Parameterized testing
Parameterized test can be used for method with processing depending on incoming data, but always producing same response. Usually it is utility methods.
Code Block | ||
---|---|---|
| ||
@Nested @DisplayName("existsById") class ExistsById { private static final UUID ENTITY_ID = UUID.randomId(); @ParameterizedTest @CsvSource({"true", "false"}) void positive(boolean entityExistsResult) { when(repository.existsById(ENTITY_ID)).thenReturn(entityExistsResult); var result = service.existsById(ENTITY_ID); assertThat(result).isEqualTo(entityExistsResult); } } |
Unit tests validation
Mutation tests can be used to verify the quality of written unit tests
Parameterized tests must be used for utility classes or methods whenever possible (add example, simple guide)
Property / Data-driven testing - as an additional type of validation
Tests must be as simple as possible, and they must be fast (there is no need to test cache expiration for 12 seconds if it can be done using smaller values (100 ms as an example)
Integration tests
Use API calls to verify behavior
Always verify positive flow using integration tests
All integrations with outside applications/databases performed using https://testcontainers.com/
Real-service injections to the method are prohibited
In exceptional cases, they can be injected using
@Authowired ProductionComponent component
notation at class level
Asserts must be as simple as possible
Code Block language java mockMvc.perform(get("/entities") .header(TENANT, TENANT_ID) .andExpect(status().isOk()) .andExpect(content().json(asJsonString(readTemplate("/json/responses/entity.json")), true)));
Code Block language java mockMvc.perform(post("/entities") .content(capabilityToCreateAsJson) .header(TENANT, TENANT_ID) .contentType(APPLICATION_JSON)) .andExpect(status().isCreated()) .andExpect(content().json(asJsonString(readTemplate("/json/responses/entity.json")))) .andExpect(jsonPath("$.id").value(notNullValue())) .andExpect(jsonPath("$.metadata.createdBy").value(equalTo(USER_ID))) .andExpect(jsonPath("$.metadata.createdDate").value(notNullValue())) .andExpect(jsonPath("$.metadata.modifiedBy").doesNotExist()) .andExpect(jsonPath("$.metadata.modifiedDate").doesNotExist());
Verify that external data is created in outside service
Java HTTP client can be used to verify data in Keycloak
JDBC Helpers classes to verify database data performing native SQL requests, if it cannot be checked using API
Kong created routes can be verified using wiremock and keycloak native functionality mgr-tenant-entitlements: EntitilementRoutesIT