springboot unit testing technology

In the whole software delivery process, the unit test stage is the stage where problems can be found at the earliest, and problems can be repeatedly regressed. The more adequate the test is done in the unit test stage, the better the software quality can be guaranteed.
For specific code, refer to the sample project https://github.com/qihaiyan/springcamp/tree/master/spring-unit-test

I. Overview

A functional full-link test often depends on many external components, such as databases, redis, kafka, third-party interfaces, etc. The execution environment of unit tests may be limited by the network and cannot access these external services. Therefore, we hope that through some technical means, we can use unit testing technology to conduct complete functional testing without relying on external services.

2. Test of REST interface

springboot provides the testRestTemplate tool for testing interfaces in unit tests. This tool only needs to specify the relative path of the interface, and does not need to specify the domain name and port. This feature is very useful, because the web service of springboot's unit test runtime environment is a random port, which is specified by the following annotation:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

/remoteHere is how to test the interface we developed through testRestTemplate :

    @Test
    public void testRemoteCallRest() {
    
    
        String resp = testRestTemplate.getForObject("/remote", String.class);
        System.out.println("remote result : " + resp);
        assertThat(resp, is("{\"code\": 200}"));
    }

3. Dependence on third-party interfaces

In the above example, our remote interface will call a third-party interface http://someservice/foo. Our build server may be restricted by the network and cannot access this third-party interface, which will cause the unit test to fail to execute. We can use MockRestServiceServerthe tools to solve this problem.

First define a MockRestServiceServer variable

private MockRestServiceServer mockRestServiceServer;

Initialization during the initialization phase of unit tests

    @Before
    public void before() {
    
    
        mockRestServiceServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();

        this.mockRestServiceServer.expect(manyTimes(), MockRestRequestMatchers.requestTo(Matchers.startsWithIgnoringCase("http://someservice/foo")))
                .andRespond(withSuccess("{\"code\": 200}", MediaType.APPLICATION_JSON));

    }

In this way, when the interface is called in our unit test program http://someservice/foo, the return value will be returned {"code": 200}instead of actually accessing the third-party interface.

Fourth, the dependence of the database

The dependence on the database is relatively simple, just use the embedded database h2 directly, and all database operations are performed in the embedded database h2.

The gradle configuration is taken as an example:

testImplementation 'com.h2database:h2'

The database connection in the unit test configuration file uses h2:

spring:
  data:
    url: jdbc:h2:mem:ut;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:

Database operations can be performed directly in the unit test program:

MyDomain myDomain = new MyDomain();
myDomain.setName("test");
myDomain = myDomainRepository.save(myDomain);

When we call the interface to query the records in the database, we can correctly query the results:

MyDomain resp = testRestTemplate.getForObject("/db?id=" + myDomain.getId(), MyDomain.class);
System.out.println("db result : " + resp);
assertThat(resp.getName(), is("test"));

When the interface returns paged data, special processing is required, otherwise a json serialization error will be reported.

Define your own Page class:

    public class TestRestResponsePage<T> extends PageImpl<T> {
    
    
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public TestRestResponsePage(@JsonProperty("content") List<T> content,
                                @JsonProperty("number") int number,
                                @JsonProperty("size") int size,
                                @JsonProperty("pageable") JsonNode pageable,
                                @JsonProperty("empty") boolean empty,
                                @JsonProperty("sort") JsonNode sort,
                                @JsonProperty("first") boolean first,
                                @JsonProperty("totalElements") long totalElements,
                                @JsonProperty("totalPages") int totalPages,
                                @JsonProperty("numberOfElements") int numberOfElements) {
    
    

        super(content, PageRequest.of(number, size), totalElements);
    }

    public TestRestResponsePage(List<T> content) {
    
    
        super(content);
    }

    public TestRestResponsePage() {
    
    
        super(new ArrayList<>());
    }
}

The call interface returns a custom Page class:

RequestEntity<Void> requestEntity = RequestEntity.get("/dbpage").build();
ResponseEntity<TestRestResponsePage<MyDomain>> pageResp = testRestTemplate.exchange(requestEntity, new ParameterizedTypeReference<TestRestResponsePage<MyDomain>>() {
    
    
    });
System.out.println("dbpage result : " + pageResp);
assertThat(pageResp.getBody().getTotalElements(), is(1L));

Since the returned result is generic, testRestTemplate.exchangea method needs to be used, and the get method does not support returning generics.

Five, redis dependency

There is an open source redis mockserver on the Internet, which imitates most of the redis instructions, we only need to import this redis-mockserver.
The original version was developed by a Chinese. In the example, a foreigner’s fork version was introduced. Some instructions were added, but the source code could not be found. I forked another version, adding setex and zscore instructions. If necessary You can compile it yourself. Code connection https://github.com/qihaiyan/redis-mock

The gradle configuration is taken as an example:

testImplementation 'com.github.fppt:jedis-mock:0.1.16'

The database connection in the unit test configuration file uses redis mockserver:

spring:
  redis:
    port: 10033

Add a separate redis configuration file for starting redis mockserver in unit tests:

@TestConfiguration
public class TestRedisConfiguration {
    
    

    private final RedisServer redisServer;

    public TestRedisConfiguration(@Value("${spring.redis.port}") final int redisPort) throws IOException {
    
    
        redisServer = RedisServer.newRedisServer(redisPort);
    }

    @PostConstruct
    public void postConstruct() throws IOException {
    
    
        redisServer.start();
    }

    @PreDestroy
    public void preDestroy() {
    
    
        redisServer.stop();
    }
}

6. Kafka's dependency

Spring provides a kafka test component that can start an embedded kafka service EmbeddedKafka during unit testing to simulate real kafka operations.

The gradle configuration is taken as an example:

testImplementation "org.springframework.kafka:spring-kafka-test"

Initialize EmbeddedKafka through ClassRule, there are two topics: testEmbeddedIn and testEmbeddedOut.

    private static final String INPUT_TOPIC = "testEmbeddedIn";
    private static final String OUTPUT_TOPIC = "testEmbeddedOut";
    private static final String GROUP_NAME = "embeddedKafkaApplication";

    @ClassRule
    public static EmbeddedKafkaRule embeddedKafkaRule = new EmbeddedKafkaRule(1, true, INPUT_TOPIC, OUTPUT_TOPIC);

    public static EmbeddedKafkaBroker embeddedKafka = embeddedKafkaRule.getEmbeddedKafka();

    private static KafkaTemplate<String, String> kafkaTemplate;

    private static Consumer<String, String> consumer;

    @BeforeClass
    public static void setup() {
    
    

        Map<String, Object> senderProps = KafkaTestUtils.producerProps(embeddedKafka);
        DefaultKafkaProducerFactory<String, String> pf = new DefaultKafkaProducerFactory<>(senderProps);
        kafkaTemplate = new KafkaTemplate<>(pf, true);

        Map<String, Object> consumerProps = KafkaTestUtils.consumerProps(GROUP_NAME, "false", embeddedKafka);
        DefaultKafkaConsumerFactory<String, String> cf = new DefaultKafkaConsumerFactory<>(consumerProps);
        consumer = cf.createConsumer();
        embeddedKafka.consumeFromAnEmbeddedTopic(consumer, OUTPUT_TOPIC);
    }

In the configuration file of the unit test program, you can specify the two kafka topics

cloud.stream.bindings:
    handle-out-0.destination: testEmbeddedOut
    handle-in-0.destination: testEmbeddedIn
    handle-in-0.group: embeddedKafkaApplication

Guess you like

Origin blog.csdn.net/haiyan_qi/article/details/115837297