API REST com春季+ Testes com MockMVC +Documentaçãocom Swagger

news/2024/11/19 11:18:52/

Introdução

REST API REST 100%功能性PostgreSQL数据库,Spring数据JPA参数,作为咨询机构,DTO数据包,Duals数据集,dadas总线数据集, MockMVC和Swagger的形式化文档,集成了有效的Nossos端点证明。

完全免费的Github播客:

Clone no Github

Dependências

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

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

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

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency> <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.6.1</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.6.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency><dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest-core</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency></dependencies>

Spring boot

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

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 data 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',春季数据,JPA que facilita ainda mais,作为banco de dados顾问,alémde serútilmais a frente durante os证明人,ondeserápossívelcriar unsrepositóbandecaçãoque n dados durante essa etapa。

没有储存库,乌鲁木齐,多米尼加共和国找到所有()您可以通过以下方式访问您的网站:Passando um objeto depaginação,somente pelo nome do medodo,seráinferido uma busca por todos os elementos,e ummétodode busca com uma uma e a customizada。

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<Produto, Integer> {public Page<Produto> findAll(Pageable pageable);@Query("SELECT p FROM Produto p "+ "WHERE lower(nome) like %:busca% ")public Page<Produto> busca(@Param("busca") String busca, Pageable pageable);}

编辑arquivo application.properties,然后在com上进行配置,在api上配置,然后在caminhopadrão和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 class。 E com作为春季必不可少的东西。

阿诺塔@RequestMapping(“ / produtos”)irádefinir o caminhopadrãodesse端点,nesse caso / produtos。

准入门槛的端点许可,反作用的Ang角,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());@AutowiredProdutoRepository produtoRepository;...}

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

GET

可以在vamos crimar的首要端点处找到要处理的产品,并通过@GetMapping()通知响应者来获取GET请求。 Defino osparâmetrosque essa URL,podo receber,todos opcionais,para que possa paginar,ordenar e fazer uma busca。

@ApiOperation(value = "Lista os produtos")@GetMapping()public Page<Produto> 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<Produto> 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.

URL / 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<Produto> listar(@PathVariable Integer id) {Optional<Produto> rastreador = produtoRepository.findById(id);if (!rastreador.isPresent()) {return ApiError.notFound("Produto não encontrado");}return new ResponseEntity<>(rastreador.get(), HttpStatus.OK);}

POST e PUT

Proparatoer 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 atributopublic 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<Produto> criar(@RequestBody ProdutoDTO dto, UriComponentsBuilder ucBuilder) {try {//Crio um objeto da entidade preenchendo com os valores do DTO e validandoProduto 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 APIif (novo == null) {return ApiError.badRequest("Ocorreu algum erro na criação do produto");}//Se foi criado com sucesso, retorno o objeto criadoreturn 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,根据需要确认的面额,要求保留的权利,以及最终证明书或原始产品的永久证明。

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

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

@ApiOperation(value = "Atualiza um Rastreador Equipamento")@PutMapping(value = "/{id}")public ResponseEntity<Produto> atualizar(@PathVariable("id") int id, @RequestBody ProdutoDTO dto) {try {Optional<Produto> 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órioProduto atualizado = produtoRepository.save(produtoAtual.get());//Se ocorreu algum erro, retorno esse erro para a APIif (atualizado == null) {return ApiError.internalServerError("Erro na atualização do produto");}//Se foi criado com sucesso, retorno o objeto atualizadoreturn 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");}}

DELETE

一个简单的批注,简单,可验证的版本,一个可删除的版本库。

@ApiOperation(value = "Remove um produto")@DeleteMapping(value = "/{id}")public ResponseEntity<Produto> deletar(@PathVariable Integer id) {Optional<Produto> produto = produtoRepository.findById(id);if (!produto.isPresent()) {return ApiError.notFound("Produto não encontrado");} else {produtoRepository.deleteById(id);}return new ResponseEntity<>(HttpStatus.OK);}

Documentação com Swagger

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

请将SwaggerConfig com类作为必要的信息,以确保控制人员的需要。

@Configuration
@EnableSwagger2
public class SwaggerConfig extends WebMvcConfigurationSupport {@Beanpublic Docket api() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(apis()).paths(PathSelectors.any()).build();}private Predicate<RequestHandler> 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();}@Overrideprotected 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。


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.

Testes

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 controladorprivate final String BASE_URL = "/produtos";//Instância do ObjectMapper para trabalhar com JSONprivate ObjectMapper objectMapper;//Controlador REST tratado por meio de injeção de dependências@Autowiredprivate ProdutoController restController;//Instância do MockMVCprivate MockMvc mockMvc;//Instância do mock repository@MockBeanprivate ProdutoRepository mockRepository;@Beforepublic void setUp() {objectMapper = new ObjectMapper();mockMvc = MockMvcBuilders.standaloneSetup(restController).build();}...}

验证协议的最终目的是什么,验证协议的必要性,验证协议的有效性,验证请求的有效性 o终结点端点。

@Testpublic 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 nos outros端点。

@Testpublic void buscar_id_404() throws Exception {mockMvc.perform(get(BASE_URL + "/2")).andExpect(status().isNotFound());}@Testpublic 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));}@Testpublic 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)));}@Testpublic 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

Mostrei nesse post um simples exemplo de uma API REST com Spring, o código completo pode ser encontrado no Github: https://github.com/pcollares/api-rest-spring

Referências

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/

Spring

  • 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 data 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/

MockMVC

  • 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/

Swagger

  • 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ção e ordenaçã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


http://www.ppmy.cn/news/336632.html

相关文章

小米fortnite_我如何仅用1,001美元(与我12岁的孩子)构建自己的Fortnite DIY游戏平台

小米fortnite So, my 12-year old decided that she needed a more powerful computer for her favorite video game, Fortnite. And I promised her, years ago, that we’d find a time to custom-build her own computer. 因此&#xff0c;我12岁的她决定为自己喜欢的视频游…

QQ再次被大规模盗号

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; QQ被大规模盗号已经不是一次两次了&#xff0c;感觉腾讯好像没有太好的解决办法。 昨天(周日)QQ又出下了大规模盗号情况&#xff1a;大量用户反馈称遇到了自己或是身边的朋友、家人、同事等被盗号…

教你如何找回被盗QQ的好友

现在QQ盗号让人防不胜防&#xff0c;一不小心QQ号就被盗了。号盗了没有什么&#xff0c;可以再申请一个(不过之前还可以试试把密码找回来&#xff0c;如果里面的朋友没了&#xff0c;那可怎么办呀&#xff1f;下面介绍如何让好友都回来的方法&#xff1a; 1.首先必须保证你的电…

谨防qq盗号

各位朋友们注意了&#xff01; 最近qq盗号现象频繁&#xff0c;本人的同学与老师近两个月总被盗号&#xff0c;要么是发一个所谓的“好友账号申诉网站”&#xff0c;要么就是下图的二维码 千万别扫&#xff01;不知道有没有投诉成功&#xff0c;安全起见还是不要扫码 虽然但是…

QQ被盗是怎么一回事?

最近网络上QQ号被盗一事闹得沸沸扬扬&#xff0c;那么这究竟是怎么一回事呢&#xff1f;我们应该如何分辨和防范呢&#xff1f; 首先我们先来说一下QQ号被盗的原因。其实这种事情真的非常简单。社会上有许多防范意识不够强的人&#xff0c;经常贪恋小便宜&#xff0c;或者戒备…

一个QQ空间的钓鱼盗号过程揭露,大家谨防上当

1.盗号过程 很久没有用过QQ空间了&#xff0c;今天突然QQ弹出一条消息&#xff0c;说我的一个好友留言中提到了我&#xff0c;但是我却也打不开这个链接。 于是&#xff0c;我就去她的空间留言板查看。发现第一条留言&#xff0c;是一个二维码 扫描之后&#xff0c;进入到一…

QQ如何防盗

QQ是大家上网最常使用的通讯工具之一&#xff0c;很多人都拥有自己的QQ号码&#xff0c;那你有没有碰到过号码被盗的惨事呢&#xff1f;盗号者使用你的号码干一些损人之事&#xff0c;知道内情的好友还好说&#xff0c;不知道的还以为就是你呢&#xff01;你不仅蒙受了不白之冤…

关于qq被盗问题.....

最近有好几个朋友qq号码都被盗了 其实偷qq号码的也没什么追求 无非就是勒索二三十块钱 坐下来想象 其实人家也不容易 费那么大劲把你的qq弄到手 人家也付出了劳动也应该有回报 如果你是比较富裕的那一类人的话 就给撂几个钱赎回来算了 如果你和我一样都是 贫下中农无产阶级 那是…