Spring boot (6) - unit testing

Posted by Kiubbo on Mon, 27 May 2019 00:21:34 +0200

Personal accumulation, please do not reproduce privately, please contact before reproducing
Code and article resources https://github.com/jedyang/DayDayUp/tree/master/java/springboot
Reading notes based on Spring Book CookBook focus on personal understanding and practice rather than translation.

test

For a comprehensive analysis of unit testing, see my article, which only talks about spring boot.
unit testing

The different versions of springboot tested vary considerably. Here we use 1.5.*

In the project we created, the required directory structure BookPub Application Tests already exists.

Test rest requests

package org.test.bookpub;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import org.test.bookpub.entity.Book;

import static junit.framework.TestCase.assertNotNull;
import static org.junit.Assert.assertEquals;

@RunWith(SpringRunner.class)// Spring JUnit support, which introduces Spring-Test framework support
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class BookPubApplicationTests {
    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void webappBookIsbnApi() {
//        Book book = restTemplate.getForObject("/books/1001", Book.class);
        Book book = restTemplate.getForObject("http://localhost:8080/books/1001", Book.class);
        System.out.println(book);
        assertNotNull(book);
        assertEquals("yunsheng", book.getPublisher().getName());
    }

}
  1. webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT is a port that uses configuration.
    Another is the webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT random port.
    Because restTemplate. getForObject ("http://localhost:8080/books/1001"), Book. class) is written to death url, so any one can be set here.
  2. Book book = restTemplate.getForObject("/books/1001", Book.class); this uses the default protocol and domain name of the code. Because our default configuration is https. There will be problems with direct use here.

Test database connection

Add a test method

@Autowired
private BookRepository bookRepository;

// Test database can be connected normally
@Test
public void contextLoads() {
    assertEquals(1, bookRepository.count());
}

Server-side testing

TestRestTemplate is a test that simulates requests from clients. Another approach is to test the controller layer directly on the server side using MockMvc objects. It's like starting the server.

@Autowired
private WebApplicationContext context;

private MockMvc mockMvc;
@Before
public void setupMockMvc() {
    mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}

@Test
public void webappBookApi() throws Exception {
    //MockHttpServletRequestBuilder.accept method is to set the content type that the client can recognize.
    //MockHttpServletRequestBuilder.contentType, which sets the Content-Type field in the request header to represent the content type of the request body
    mockMvc.perform(get("http://localhost:8080/books/1001")
            .accept(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(status().isOk())
            .andExpect(content().string(containsString("yunsheng")))
            .andExpect(jsonPath("$.isbn").value("1001"));
}

Database insertion data

Previously, we put the code to insert a record into the database in Startup Runner, which is not very appropriate. Now replace it with unit testing.
There are several ways to do this.

  1. Using mapping tools like hibernate, it scans the classes annotated by @Entity and automatically creates the corresponding tables. Then use import.sql to create data.
  2. Using spring jdbc, table structure is defined by schema.sql file, and data.sql is used to create data.

practice

Using hibernate

  1. Comment out the code in Startup Runner before

      @Override
     public void run(String... strings) throws Exception {
         logger.info("dataSource:" + ds.toString());
         // Add a record
     //        Author author = new Author("yunsheng", "yang");
     //        author = authorRepository.save(author);
     //        Publisher publisher = new Publisher("yunsheng");
     //        publisher = publisherRepository.save(publisher);
     //        Book book = new Book("1001", "test1", author, publisher);
     //        Book book1 = bookRepository.save(book);
         logger.info("number of books:" + bookRepository.count());
     }
    
  2. Configuration in application.properties

     spring.jpa.hibernate.ddl-auto=create-drop
    
  3. Create import.sql file under resources

     INSERT INTO author (id, first_name, last_name) VALUES (1, 'yunsheng', 'yang');
     INSERT INTO publisher (id, name) VALUES (1, 'yunsheng');
     INSERT INTO book (isbn, title, author_id, publisher_id) VALUES ('1001', 'Spring Boot Recipes', 1,1);
    
  4. Run the test case and pass it.

Using jdbc mode

  1. Configuration in application.properties

    spring.jpa.hibernate.ddl-auto=none
    Don't do anything even if Hibernate relies on being

  2. Create schema.sql to create tables

     -- Create syntax for TABLE 'author'
     DROP TABLE IF EXISTS `author`;
     CREATE TABLE `author` (
       `id`         BIGINT(20) NOT NULL AUTO_INCREMENT,
       `first_name` VARCHAR(255)        DEFAULT NULL,
       `last_name`  VARCHAR(255)        DEFAULT NULL,
       PRIMARY KEY (`id`)
     );
     -- CREATE syntax FOR TABLE 'publisher'
     DROP TABLE IF EXISTS `publisher`;
     CREATE TABLE `publisher` (
       `id`   BIGINT(20) NOT NULL AUTO_INCREMENT,
       `name` VARCHAR(255)        DEFAULT NULL,
       PRIMARY KEY (`id`)
     );
     -- CREATE syntax FOR TABLE 'reviewer'
     DROP TABLE IF EXISTS `reviewer`;
     CREATE TABLE `reviewer` (
       `id`         BIGINT(20) NOT NULL AUTO_INCREMENT,
       `first_name` VARCHAR(255)        DEFAULT NULL,
       `last_name`  VARCHAR(255)        DEFAULT NULL,
       PRIMARY KEY (`id`)
     );
     -- CREATE syntax FOR TABLE 'book'
     DROP TABLE IF EXISTS `book`;
     CREATE TABLE `book` (
       `id`           BIGINT(20) NOT NULL AUTO_INCREMENT,
       `description`  VARCHAR(255)        DEFAULT NULL,
       `isbn`         VARCHAR(255)        DEFAULT NULL,
       `title`        VARCHAR(255)        DEFAULT NULL,
       `author_id`    BIGINT(20)          DEFAULT NULL,
       `publisher_id` BIGINT(20)          DEFAULT NULL,
       PRIMARY KEY (`id`),
       CONSTRAINT `FK_publisher` FOREIGN KEY (`publisher_id`) REFERENCES
         `publisher` (`id`),
       CONSTRAINT `FK_author` FOREIGN KEY (`author_id`) REFERENCES `author`
       (`id`)
     );
     -- CREATE syntax FOR TABLE 'book_reviewers'
     DROP TABLE IF EXISTS `book_reviewers`;
     CREATE TABLE `book_reviewers` (
       `book_id`      BIGINT(20) NOT NULL,
       `reviewers_id` BIGINT(20) NOT NULL,
       CONSTRAINT `FK_book` FOREIGN KEY (`book_id`) REFERENCES `book`
       (`id`),
       CONSTRAINT `FK_reviewer` FOREIGN KEY (`reviewers_id`) REFERENCES
         `reviewer` (`id`)
     );
    
  3. Create data.sql to insert data
    Just copy it as import.sql

  4. Run test cases, pass

By default, Hibernate uses import.sql. jdbc uses schema.sql and data.sql.
If you want to replace schema.sql or data.sql with another name, Spring Book also provides the corresponding configuration properties, spring.datasource.schema and spring.datasource.data.

tips, I made a mistake here, writing reviewer as an internal class of publisher, where creating tables will fail and have been modified.

mock database operation

In practice, the more common way we write single test is to mock out database operations. Here's a demonstration of using mockito.

package org.test.bookpub;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.test.bookpub.repository.PublisherRepository;

import static org.junit.Assert.assertEquals;

@RunWith(SpringRunner.class)
@SpringBootTest
public class PublisherRepositoryTests {
    @MockBean
    private PublisherRepository repository;

    @Before
    public void setupPublisherRepositoryMock() {
        Mockito.when(repository.count()).thenReturn(100L);
    }

    @Test
    public void publishersExist() {
        assertEquals(100, repository.count());
    }

    @After
    public void resetPublisherRepositoryMock() {
        Mockito.reset(repository);
    }
}

Create a new class demonstration, mockito more usage, can be google.

I don't really understand a comment here. cookbook has a bunch of code.
In fact, I have regretted writing here. cookbook is too old.
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
Look directly at the official documents. You have everything you want.

Topics: Spring SQL Junit Database