API REST comSpring+ Testes com MockMVC +Documentaçãocom Swagger

简介

REST API REST 100%功能性PostgreSQL数据库,Spring数据JPA Para Facilitar作为顾问,PadãoDTO parainclusãoatualualçaçãodos dados,listar grandesQuantidades de dados pagina, MockMVC和Swagger的形式化文档,集成了有效的Nossos端点证明。

完全免费的Github播客:

克隆之Github

依赖关系

正如dependênciasna nossaaplicaçãosão:

  • Spring靴
  • 冬眠
  • Spring数据JPA
  • 模拟MVC
  • 昂首阔步

Todasserãotratadas pelo Maven,segundo o pom.xml abaixo:

 
         
            org.springframework.boot 
            spring-boot-starter-web 
        
         
            org.springframework.boot 
            spring-boot-starter-test
            test
         
        
            io.springfox
            springfox-swagger2
            2.6.1
        
        
            io.springfox
            springfox-swagger-ui
            2.6.1
        
        
            junit
            junit
            test
        
        
            org.hamcrest
            hamcrest-core
            test
        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            org.springframework.boot
            spring-boot-devtools
            runtime
        
        
            org.postgresql
            postgresql
        
    

Spring靴

Spring靴子是必需的,但必须由执行官签发,但必须由执行官负责,并必须执行生产性保护措施。

Java的Para isso basta criarmos uma classe com ométodomain:

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

Hibernate e Spring数据JPA

Spring数据联合使用的冬眠性数据JPA自动化类,动态地图类和多米尼加共和国类。

Primeiramente,vamos criar uma classe Produto.java可以用作在地图上实现现实的必要工具。

As anotações de @ApiModelProperty serão usadas para a documentação com o Swagger, que veremos mais a frente.

@Entity
@Table(name = "produto")
public class Produto {

    @Id
    @SequenceGenerator(name = "produto_seq", sequenceName = "produto_seq", allocationSize = 1)
    @GeneratedValue(generator = "produto_seq", strategy = GenerationType.AUTO)
    private int id;

    private String nome;

    private double valor;

    @ApiModelProperty(notes = "Identificador do produto")
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @ApiModelProperty(notes = "Nome do produto")
    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    @ApiModelProperty(notes = "Valor do produto")
    public double getValor() {
        return valor;
    }

    public void setValor(double valor) {
        this.valor = valor;
    }

}

Depois criaremos um'Repository',Spring数据,JPA que facilita ainda mais作为banco de dados的顾问,alémde serútilmais a frente durante os证明人,ondeserápossívelcriar unsrepositóbandeseçãoquenão dados durante essa etapa。

没有可存储的存储库,无处不在的criamos doismétodos,没有findAll()的 passando um objeto depaginação,somente pelo nome dométodo,seráinferido uma busca por todos os elementos,e ummétodode busca com uma顾问。

O 'Repository' são interfaces, que o Spring irá tratar como injeção de dependências quando forem invocadas mais a frente.

@Repository
public interface ProdutoRepository extends PagingAndSortingRepository {

    public Page findAll(Pageable pageable);

    @Query("SELECT p FROM Produto p "
            + "WHERE lower(nome) like %:busca% ")
    public Page busca(@Param("busca") String busca, Pageable pageable);

}

编辑arquivo application.properties,并配置api,comin camihopadrãoeo modo deinicialização。 PostgreSQL的Devemos criartambémo banco de dados e um esquema。

#debug
debug=true

#api
server.servlet.context-path=/api

#conexão
javax.persistence.create-database-schemas=true
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/teste
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.properties.hibernate.default_schema=api

#define como o hibernate irá se comportar quanto a criação do esquema
#create: Apaga e recria todo o esquema
#update: Atualiza o mapeamento
spring.jpa.hibernate.ddl-auto=update

#define se irá executar o 'data.sql'
#always: Sempre irá executar o data.sql
#never: Nunca irá executar o data.sql
spring.datasource.initialization-mode=never

#codificação do data.sql
spring.datasource.sqlScriptEncoding=UTF-8

Controlador REST

Para disponibilizar nossa API publica,PromotoController.java para colocar todos os端点引用了essa类。 E com作为Spring必做之事。

@RequestMapping(“ / produtos”)的最后一个终结点是nesse caso / produtos。

准许使用无提示字体的端点,反作用于Angular,precisamos habilitar或CORS(跨原始资源共享),以及@@ CrossOrigin。

As anotações @Api e @ApiOperation servirão para documenta pelo swagger, que veremos mais a frente.

@RestController
@RequestMapping("/produtos")
@CrossOrigin
@Api(tags = "Produtos", description = "API de produtos")
public class ProdutoController {

    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    @Autowired
    ProdutoRepository produtoRepository;

...

}

阿西玛(Acima),控制权的基础设施建设,必要的备件,生产资料的备件,生产资料的储备金,春天的依赖关系。

得到

可以在vamos criar的主要端点上找到要处理的产品或服务,请发送电子邮件至@GetMapping(),并要求获得GET请求。 Defino osparâmetrosque essa URL,pod receber,todos opcionais,para que possa paginar,ordenar e fazer uma busca。

@ApiOperation(value = "Lista os produtos")
    @GetMapping()
    public Page listar(
            @RequestParam(
                    value = "page",
                    required = false,
                    defaultValue = "0") int page,
            @RequestParam(
                    value = "size",
                    required = false,
                    defaultValue = "10") int size,
            @RequestParam(
                    value = "sort",
                    required = false) String sort,
            @RequestParam(
                    value = "q",
                    required = false) String q
    ) {
        Pageable pageable = new PageableFactory(page, size, sort).getPageable();

        Page resultPage;

        if (q == null) {
            resultPage = produtoRepository.findAll(pageable);
        } else {
            resultPage = produtoRepository.busca(q.toLowerCase(), pageable);
        }

        return resultPage;
    }

Preciso criar um objeto Pageable para passar para o repositório, para isso criei uma fábrica para facilitar essa criação, a PageableFactory.

网址/ api / produtos的示例咨询:

{
  "content": [
    {
      "id": 2,
      "nome": "Processador Intel Core i7-9700K",
      "valor": 2454
    },
    {
      "id": 3,
      "nome": "Headset Gamer HyperX Cloud Stinger - HX-HSCS-BK/NA ",
      "valor": 189.37
    },
    {
      "id": 4,
      "nome": "Teclado Mecânico Gamer HyperX Mars, RGB, Switch Outemu Bluem, US - HX-KB3BL3-US/R4 ",
      "valor": 284.11
    },
    {
      "id": 5,
      "nome": "Mouse Logitech M90 Preto 1000DPI ",
      "valor": 26.9
    },
    {
      "id": 6,
      "nome": "Gabinete C3Tech Gamer ATX sem Fonte Preto MT-G50BK",
      "valor": 119.6
    },
    {
      "id": 7,
      "nome": "Headphone Edifier Bluetooth W800BT Preto",
      "valor": 250
    },
    {
      "id": 8,
      "nome": "Kindle Novo Paperwhite, 8GB, Wi-Fi, Preto - AO0705 ",
      "valor": 418.99
    },
    {
      "id": 9,
      "nome": "SSD Kingston A400, 240GB, SATA, Leitura 500MB/s, Gravação 350MB/s - SA400S37/240G ",
      "valor": 166
    },
    {
      "id": 10,
      "nome": "HD Seagate BarraCuda, 1TB, 3.5´, SATA - ST1000DM010",
      "valor": 290
    },
    {
      "id": 11,
      "nome": "Cadeira Gamer DT3sports GT, Black - 10293-5",
      "valor": 552.41
    }
  ],
  "pageable": {
    "sort": {
      "unsorted": true,
      "sorted": false,
      "empty": true
    },
    "pageSize": 10,
    "pageNumber": 0,
    "offset": 0,
    "unpaged": false,
    "paged": true
  },
  "last": false,
  "totalPages": 2,
  "totalElements": 19,
  "numberOfElements": 10,
  "sort": {
    "unsorted": true,
    "sorted": false,
    "empty": true
  },
  "first": true,
  "size": 10,
  "number": 0,
  "empty": false
}

可能的外部建议

  • http://127.0.0.1:8080/api/produtos?page=2
  • http://127.0.0.1:8080/api/produtos?page=1&size=30
  • http://127.0.0.1:8080/api/produtos?q=teclado
  • http://127.0.0.1:8080/api/produtos?sort=valor,asc
  • http://127.0.0.1:8080/api/produtos?sort=valor,desc
  • http://127.0.0.1:8080/api/produtos?page=0&size=3&ort=valor,desc&q=intel

在端点列表上添加端点列表,例如:

@ApiOperation(value = "Busca um produto pelo id")
    @GetMapping(value = "/{id}")
    public ResponseEntity listar(@PathVariable Integer id) {
        Optional rastreador = produtoRepository.findById(id);

        if (!rastreador.isPresent()) {
            return ApiError.notFound("Produto não encontrado");
        }

        return new ResponseEntity<>(rastreador.get(), HttpStatus.OK);
    }

开机自检

Proparato inserir ou atualizar um novo produto,vamos primeiramente criar uma classe ProdutoDTO.java。

O padrão DTO (Data transfer object) é uma classe que representa a entidade com apenas os atributos necessários para serem expostos publicamente, no nosso exemplo apenas preciso, na criação ou atualização, informar no nome e valor do produto, e nunca o seu id, por isso a sua classe de DTO não possui esse atributo

public class ProdutoDTO {

    private String nome;
    private Double valor;

    @ApiModelProperty(notes = "Nome do produto")
    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public Double getValor() {
        return valor;
    }

    @ApiModelProperty(notes = "Valor do produto")
    public void setValor(Double valor) {
        this.valor = valor;
    }
}

Sendo assim,或其他代理人:

@ApiOperation(value = "Cria um novo Produto")
    @PostMapping()
    public ResponseEntity criar(@RequestBody ProdutoDTO dto, UriComponentsBuilder ucBuilder) {
        try {
            //Crio um objeto da entidade preenchendo com os valores do DTO e validando
            Produto produto = new Produto();

            if (dto.getNome() == null || dto.getNome().length() < 2) {
                return ApiError.badRequest("Informe o nome do produto");
            }
            produto.setNome(dto.getNome());

            if (dto.getValor() == null || dto.getValor() <= 0) {
                return ApiError.badRequest("Valor do produto inválido");
            }
            produto.setValor(dto.getValor());

            Produto novo = produtoRepository.save(produto);

            //Se ocorreu algum erro, retorno esse erro para a API
            if (novo == null) {
                return ApiError.badRequest("Ocorreu algum erro na criação do produto");
            }

            //Se foi criado com sucesso, retorno o objeto criado
            return new ResponseEntity<>(novo, HttpStatus.CREATED);
        } catch (Exception e) {
            LOGGER.error("Erro ao criar um produto", e);
            return ApiError.internalServerError("Ocorreu algum erro na criação do produto");
        }
    }

产品信息和徽标,徽标,密码,密码,示例,密码:

{
"nome": "Teclado Microsoft",
"valor": 124
}

Ainda nessemétodo,需要确认的面额,retornando um erro quandonãoforem atendidos os requisitos,最终的合同书或永久合同书。

没有最终的评论。 Esse processo pode ser visto abaixo。

API REST comSpring+ Testes com MockMVC +Documentaçãocom Swagger_第1张图片

产品的身份信息会引起混乱,并通知他人。

@ApiOperation(value = "Atualiza um Rastreador Equipamento")
    @PutMapping(value = "/{id}")
    public ResponseEntity atualizar(@PathVariable("id") int id, @RequestBody ProdutoDTO dto) {
        try {
            Optional produtoAtual = produtoRepository.findById(id);

            if (!produtoAtual.isPresent()) {
                return ApiError.notFound("Produto não encontrado");
            }

            if (dto.getNome() != null) {
                if (dto.getNome().length() < 2) {
                    return ApiError.badRequest("Nome do produto inválido");
                }
                produtoAtual.get().setNome(dto.getNome());
            }

            if (dto.getValor() != null) {
                if (dto.getValor() <= 0) {
                    return ApiError.badRequest("Valor do produto inválido");
                }
                produtoAtual.get().setValor(dto.getValor());
            }

            //Atualizo o objeto utilizando o repositório
            Produto atualizado = produtoRepository.save(produtoAtual.get());

            //Se ocorreu algum erro, retorno esse erro para a API
            if (atualizado == null) {
                return ApiError.internalServerError("Erro na atualização do produto");
            }

            //Se foi criado com sucesso, retorno o objeto atualizado
            return new ResponseEntity<>(atualizado, HttpStatus.CREATED);
        } catch (Exception e) {
            LOGGER.error("Erro ao atualizar um produto", e);
            return ApiError.internalServerError("Erro na atualização do produto");
        }
    }

删除

一个简单的批注,简单验证,存在的验证,存在的错误和错误的存储库。

@ApiOperation(value = "Remove um produto")
    @DeleteMapping(value = "/{id}")
    public ResponseEntity deletar(@PathVariable Integer id) {
        Optional produto = produtoRepository.findById(id);

        if (!produto.isPresent()) {
            return ApiError.notFound("Produto não encontrado");
        } else {
            produtoRepository.deleteById(id);
        }

        return new ResponseEntity<>(HttpStatus.OK);
    }

Swagger文档

O自动化,API作为动态DTO的类控制对象。

请将SwaggerConfig com类作为必要的信息,以确保控制者获得必要的信息。

@Configuration
@EnableSwagger2
public class SwaggerConfig extends WebMvcConfigurationSupport {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(apis())
                .paths(PathSelectors.any())
                .build();
    }

    private Predicate apis() {
        return RequestHandlerSelectors.basePackage("br.com.paulocollares.api.controladores.rest");
    }

    private ApiInfo apiInfo() {

        return new ApiInfoBuilder()
                .title("SPRING REST API")
                .description("Documentação das APIs REST")
                .contact(new Contact("pcollares", "www.paulocollares.com.br", null))
                .build();
    }

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

Acessando URL /api/swagger-ui.html,podemos ver adocumentaçãogerada,como no exemplo abaixo。

API REST comSpring+ Testes com MockMVC +Documentaçãocom Swagger_第2张图片

No projeto há uma classe, MainController, que redireciona a requisição da raiz para essa página, ou seja, se acessar http://127.0.0.1:8080/api/ é redirecionado para http://127.0.0.1:8080/api/swagger-ui.html.

睾丸

Para testar todos esses endpoins vamos usar o MockMVC para automatizar esse processo。 EleSerá负责提供发票和发票。

MockMVC的基础测试产品目录。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {
    ProdutoController.class
})
public class ProdutoTest {

    //URL base para acesso desse controlador
    private final String BASE_URL = "/produtos";

    //Instância do ObjectMapper para trabalhar com JSON
    private ObjectMapper objectMapper;

    //Controlador REST tratado por meio de injeção de dependências
    @Autowired
    private ProdutoController restController;

    //Instância do MockMVC
    private MockMvc mockMvc;

    //Instância do mock repository
    @MockBean
    private ProdutoRepository mockRepository;

    @Before
    public void setUp() {
        objectMapper = new ObjectMapper();
        mockMvc = MockMvcBuilders
                .standaloneSetup(restController)
                .build();
    }

...

}

验证协议的最终目的是什么,验证请求的目的是什么,验证请求的目的是什么,验证请求的有效性也要得到验证,验证请求的结果也必须是正确的o终结点端点。

@Test
    public void buscar_id_200() throws Exception {

        Produto produto = new Produto();
        produto.setId(1);
        produto.setNome("Teste");
        produto.setValor(10.0);

        when(mockRepository.findById(1)).thenReturn(Optional.of(produto));

        mockMvc.perform(get(BASE_URL + "/1"))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.nome", is("Teste")))
                .andExpect(jsonPath("$.valor", is(10.0)));

        verify(mockRepository, times(1)).findById(1);
    }

Essa mesmalógicaseráusada noros胜过内毒素。

@Test
    public void buscar_id_404() throws Exception {
        mockMvc.perform(get(BASE_URL + "/2")).andExpect(status().isNotFound());
    }

    @Test
    public void criar_200() throws Exception {

        ProdutoDTO dto = new ProdutoDTO();
        dto.setNome("Teste");
        dto.setValor(11.0);

        Produto produto = new Produto();
        produto.setId(1);
        produto.setNome(dto.getNome());
        produto.setValor(dto.getValor());

        when(mockRepository.save(any(Produto.class))).thenReturn(produto);

        mockMvc.perform(post(BASE_URL)
                .content(objectMapper.writeValueAsString(dto))
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.nome", is("Teste")))
                .andExpect(jsonPath("$.valor", is(11.0)));

        verify(mockRepository, times(1)).save(any(Produto.class));

    }

    @Test
    public void atualizar_200() throws Exception {

        ProdutoDTO dto = new ProdutoDTO();
        dto.setNome("Teste");
        dto.setValor(11.0);

        Produto produto = new Produto();
        produto.setId(1);
        produto.setNome(dto.getNome());
        produto.setValor(dto.getValor());

        when(mockRepository.findById(1)).thenReturn(Optional.of(produto));
        when(mockRepository.save(any(Produto.class))).thenReturn(produto);

        mockMvc.perform(put(BASE_URL + "/1")
                .content(objectMapper.writeValueAsString(dto))
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id", is(1)));
    }

    @Test
    public void deletar_200() throws Exception {

        Produto produto = new Produto();
        produto.setId(1);

        when(mockRepository.findById(1)).thenReturn(Optional.of(produto));

        mockMvc.perform(delete(BASE_URL + "/1"))
                .andExpect(status().isOk());

        verify(mockRepository, times(1)).deleteById(1);
    }

Conclusão

最简单的示例性UM API REST comSpring发布,可以在Github上完成: https : //github.com/pcollares/api-rest-spring

推荐人

Lista de links comreferênciaspara todos os assuntos abordados nesse post。

Conceitos

  • https://restfulapi.net/resource-naming/
  • https://blog.caelum.com.br/rest-principios-e-boas-praticas/

弹簧

  • https://spring.io/guides/gs/rest-service/
  • https://blog.algaworks.com/como-criar-web-services-restful-com-spring-boot/
  • https://www.mkyong.com/tutorials/spring-boot-tutorials/
  • https://blog.algaworks.com/injecao-de-dependencias-com-spring/

Spring数据JPA

  • https://www.mkyong.com/spring-boot/spring-boot-spring-data-jpa-postgresql/
  • https://www.mkyong.com/spring-boot/spring-boot-spring-data-jpa/
  • https://domineospring.wordpress.com/2015/05/11/facilite-seus-daos-com-o-spring-data-jpa/

模拟MVC

  • http://marcelotozzi.com/teste/java/2014/09/10/usar-o-mockmvc-nos-testes-do-spring-e-mais-maneiro.html
  • https://www.baeldung.com/integration-testing-in-spring
  • https://howtodoinjava.com/spring-boot2/spring-boot-mockmvc-example/
  • http://blog.marcnuri.com/mockmvc-spring-mvc-framework/

昂首阔步

  • http://www.marcelferry.com.br/tutoriais/documentando-suas-api-com-swagger-usando-springfox/
  • https://dzone.com/articles/swagger-generation-with-spring-boot
  • https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api

DTO

  • https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
  • https://medium.com/@msealvial/blindando-sua-api-spring-boot-com-o-padr%C3%A3o-dto-44f97020d1a0

Paginaçãoeordenação

  • https://dzone.com/articles/pagination-and-sorting-with-spring-data-jpa

[]的

From: https://dev.to/pcollares/api-rest-com-spring-testes-com-mockmvc-documentacao-com-swagger-4pk0

你可能感兴趣的:(API REST comSpring+ Testes com MockMVC +Documentaçãocom Swagger)