Callouts REST do Apex
Noções básicas de HTTP e callouts
O tipo mais simples de solicitação é a GET (que configura um método HTTP). Uma solicitação GET significa que o remetente quer receber informações sobre um recurso do servidor. Quando o servidor recebe e processa essa solicitação, ele envia as informações necessárias ao destinatário. As solicitações GET são como navegar para um endereço no navegador. Quando visitamos uma página da Web, o navegador faz uma solicitação GET nos bastidores. No navegador, o resultado é a exibição de uma nova página HTML. No caso dos callouts, o resultado é o objeto da resposta.
Para ilustrar como as solicitações GET funcionam, abra seu navegador e acesse esta URI: https://th-apex-http-callout.herokuapp.com/animals. O seu navegador exibirá uma lista de animais em um formato esquisito porque o serviço enviará a resposta em um formato chamado JSON. Às vezes, as respostas de uma solicitação GET também são enviadas em formato XML.
A seguir, veremos descrições de métodos HTTP comuns.
Método HTTP | Descrição |
---|---|
GET | Recuperar dados identificados por uma URL. |
POST | Criar um recurso ou publicar dados no servidor. |
DELETE | Excluir um recurso identificado por uma URL. |
PUT | Criar ou substituir o recurso enviado no corpo da solicitação. |
Quando tiver um tempinho livre, dê uma olhada na lista completa de métodos HTTP que está na seção Recursos.
Além do método HTTP, cada solicitação define uma URI, que corresponde ao endereço do ponto de extremidade no qual o serviço fica localizado. Por exemplo, este poderia ser um ponto de extremidade: http://www.example.com/api/resource. No caso da unidade “Noções básicas de HTTP e callouts”, o ponto de extremidade é: https://th-apex-http-callout.herokuapp.com/animals.
Quando o servidor processa a solicitação, ele envia um código de estado na resposta. Esse código de estado indica se a solicitação foi processada corretamente ou se houve alguma falha. Quando a solicitação dá certo, o servidor envia o código de estado 200. Você já deve ter visto outros códigos de estado, como o 404 (para arquivos que não são encontrados) ou o 500 (para erros internos do servidor).
Caso ainda tenha um tempo após navegar pela lista de métodos HTTP, confira a lista de todos os códigos de estado de resposta na seção Recursos. Caso você não esteja conseguindo dormir direito, talvez esses dois recursos ajudem.
Como receber dados de um serviço
Antes de executar os exemplos desta unidade, você precisará autorizar a URL do ponto de extremidade do callout, https://th-apex-http-callout.herokuapp.com, seguindo os passos indicados na seção Autorizar endereços dos pontos de extremidade.
- Abra o Developer Console na engrenagem de configurações (
).
- No Developer Console, selecione .
-
Exclua o código existente e insira o trecho a seguir.
Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals'); request.setMethod('GET'); HttpResponse response = http.send(request); // If the request is successful, parse the JSON response. if (response.getStatusCode() == 200) { // Deserialize the JSON string into collections of primitive data types. Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody()); // Cast the values in the 'animals' key as a list List<Object> animals = (List<Object>) results.get('animals'); System.debug('Received the following animals:'); for (Object animal: animals) { System.debug(animal); } }
- Selecione Open Log (Abrir registro) e clique em Execute (Executar).
-
Após a abertura do registro de depuração, selecione Debug Only (Apenas a depuração) para visualizar o resultado dos demonstrativos System.debug.Os nomes dos animais serão exibidos.
O JSON do nosso exemplo é bem simples e fácil de analisar. Para estruturas JSON mais complexas, use JSON2Apex. Essa ferramenta gera códigos Apex altamente tipificados para a análise de uma estrutura JSON. Basta colar o JSON no local e a ferramenta gera o código Apex do qual você precisa. Uma maravilha!
Envio de dados para um serviço
Neste exemplo, enviaremos uma solicitação POST para o serviço da Web pedindo que o nome de um animal seja adicionado. O novo nome será adicionado ao corpo da solicitação por meio de uma sequência de caracteres JSON. O cabeçalho Content-Type da solicitação está configurado para avisar o serviço de que os dados enviados estão no formato JSON, para que o processamento dos dados seja feito da forma correta. O serviço gerará uma resposta, enviando um código de estado e uma lista de todos os animais, incluindo o que você acabou de adicionar. Se a solicitação for processada corretamente, o código de estado devolve 201 devido à criação de um recurso. A resposta será enviada ao registro de depuração sempre que não for devolvido 201.
- Abra o Developer Console na engrenagem de configurações (
).
- No Developer Console, selecione .
-
Exclua qualquer código existente e insira o trecho a seguir.
Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals'); request.setMethod('POST'); request.setHeader('Content-Type', 'application/json;charset=UTF-8'); // Set the body as a JSON object request.setBody('{"name":"mighty moose"}'); HttpResponse response = http.send(request); // Parse the JSON response if (response.getStatusCode() != 201) { System.debug('The status code returned was not expected: ' + response.getStatusCode() + ' ' + response.getStatus()); } else { System.debug(response.getBody()); }
- Selecione Open Log (Abrir registro) e clique em Execute (Executar).
- Ao ocorrer a abertura do registro de depuração, selecione Debug Only (Apenas a depuração) para visualizar o resultado do demonstrativo System.debug. O último item da lista de animais será "alce possante".
Testar callouts
Temos uma notícia boa e outra ruim sobre o teste dos callouts. A notícia ruim é que os métodos de teste do Apex não dão suporte aos callouts, e os testes com callouts dão errado. A boa notícia é que o tempo de execução do teste possibilita que o usuário faça uma “simulação” do callout. Por meio dos callouts simulados, é possível especificar a resposta que será dada no teste em vez de realmente acessar o serviço da Web. É praticamente como falar o seguinte para o tempo de execução: “Sei o que esse serviço da Web me enviará. Em vez de fazer um acesso durante o teste, já envie os dados”. Usar callouts simulados nos seus testes ajuda a garantir uma cobertura de código adequada e a evitar que alguma linha seja pulada em decorrência dos callouts.
Pré-requisitos
Antes de preparar seus testes, vamos criar uma classe com os exemplos de solicitações GET e POST que executamos de forma anônima na unidade “Envio de dados para um serviço”. Esses exemplos foram um pouco modificados para as solicitações ficarem em valores de métodos e retornos, mas eles são praticamente iguais aos exemplos anteriores.
- No Developer Console, selecione .
- Para o nome da classe, insira AnimalsCallouts e clique em OK.
- Substitua o código gerado automaticamente pela seguinte definição de classe.
public class AnimalsCallouts { public static HttpResponse makeGetCallout() { Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals'); request.setMethod('GET'); HttpResponse response = http.send(request); // If the request is successful, parse the JSON response. if (response.getStatusCode() == 200) { // Deserializes the JSON string into collections of primitive data types. Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody()); // Cast the values in the 'animals' key as a list List<Object> animals = (List<Object>) results.get('animals'); System.debug('Received the following animals:'); for (Object animal: animals) { System.debug(animal); } } return response; } public static HttpResponse makePostCallout() { Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals'); request.setMethod('POST'); request.setHeader('Content-Type', 'application/json;charset=UTF-8'); request.setBody('{"name":"mighty moose"}'); HttpResponse response = http.send(request); // Parse the JSON response if (response.getStatusCode() != 201) { System.debug('The status code returned was not expected: ' + response.getStatusCode() + ' ' + response.getStatus()); } else { System.debug(response.getBody()); } return response; } }
- Pressione Ctrl+S para salvar.
Testar um callout com o StaticResourceCalloutMock
Para testar seus callouts, use callouts simulados implementando uma interface ou usando recursos estáticos. No exemplo em questão, usamos recursos estáticos e posteriormente uma interface de simulação. O recurso estático contém o corpo de resposta que será devolvido. E lembre-se: se usar um callout simulado, a solicitação não será enviada para o ponto de extremidade. Em vez disso, o tempo de execução do Apex sabe como procurar a resposta especificada no recurso estático e apresentar exatamente essa informação. O método Test.setMock informa ao tempo de execução que callouts simulados estão sendo usados no método de teste. Vejamos os callouts simulados na prática. Em primeiro lugar, criaremos um recurso estático com uma sequência de caracteres em formato JSON que será usada para a solicitação GET.
- No Developer Console, selecione .
- Para o nome, insira GetAnimalResource.
- Para o tipo MIME, selecione text/plain (texto/simples) (apesar de estarmos usando JSON).
- Clique em Submit (Enviar).
- Na guia que abre para o recurso, insira o conteúdo a seguir. Confira se está tudo na mesma linha, sem gerar uma quebra para a próxima. Esse é o conteúdo devolvido pelo callout simulado. Um conjunto de três criaturas da floresta.
{"animals": ["pesky porcupine", "hungry hippo", "squeaky squirrel"]}
- Pressione Ctrl+S para salvar.
Pronto, você criou um recurso estático! Agora, vamos adicionar um teste para o callout que use esse recurso.
- No Developer Console, selecione .
- Para o nome da classe, insira AnimalsCalloutsTest e clique em OK.
- Substitua o código gerado automaticamente pela seguinte definição de classe de teste.
@isTest private class AnimalsCalloutsTest { @isTest static void testGetCallout() { // Create the mock response based on a static resource StaticResourceCalloutMock mock = new StaticResourceCalloutMock(); mock.setStaticResource('GetAnimalResource'); mock.setStatusCode(200); mock.setHeader('Content-Type', 'application/json;charset=UTF-8'); // Associate the callout with a mock response Test.setMock(HttpCalloutMock.class, mock); // Call method to test HttpResponse result = AnimalsCallouts.makeGetCallout(); // Verify mock response is not null System.assertNotEquals(null,result, 'The callout returned a null response.'); // Verify status code System.assertEquals(200,result.getStatusCode(), 'The status code is not 200.'); // Verify content type System.assertEquals('application/json;charset=UTF-8', result.getHeader('Content-Type'), 'The content type value is not expected.'); // Verify the array contains 3 items Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(result.getBody()); List<Object> animals = (List<Object>) results.get('animals'); System.assertEquals(3, animals.size(), 'The array should only contain 3 items.'); } }
- Pressione Ctrl+S para salvar.
- Selecione . Se não selecionar a opção Sempre executar de forma assíncrona, as execuções de teste com apenas uma classe serão feitas em sincronia. É possível abrir registros a partir da guia Testes apenas para execuções de testes síncronas.
- Para executar o teste, selecione .
- Na lista Classes de teste, selecione AnimalsCalloutsTest.
- Clique em .
O resultado do teste será exibido na guia Testes com uma ID de execução de teste. Quando a execução do teste terminar, expanda a execução de teste para ver os detalhes. Agora, clique duas vezes em AnimalCallouts no painel Cobertura de código geral para ver que linhas foram incluídas nos seus testes.
Testar um callout com HttpCalloutMock
Oferecemos uma implementação da interface HttpCalloutMock para testar seus callouts de POST. Essa interface possibilita que você especifique a resposta enviada para o método respond. Sua classe de teste fará o tempo de execução do Apex enviar uma resposta falsa chamando o Test.setMock de novo. Para o primeiro argumento, passe HttpCalloutMock.class. Para o segundo argumento, passe uma nova instância de AnimalsHttpCalloutMock, que é sua interface de implementação do HttpCalloutMock. (Escreveremos AnimalsHttpCalloutMock no próximo exemplo.)
Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock());
Agora, adicione a classe que implementa a interface do HttpCalloutMock para interceptar o callout. Se um callout HTTP for invocado em um contexto de teste, o callout não é realizado. Em vez disso, você receberá a resposta da simulação especificada na implementação do método respond em AnimalsHttpCalloutMock.
- No Developer Console, selecione .
- Para o nome da classe, insira AnimalsHttpCalloutMock e clique em OK.
- Substitua o código gerado automaticamente pela seguinte definição de classe.
@isTest global class AnimalsHttpCalloutMock implements HttpCalloutMock { // Implement this interface method global HTTPResponse respond(HTTPRequest request) { // Create a fake response HttpResponse response = new HttpResponse(); response.setHeader('Content-Type', 'application/json'); response.setBody('{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}'); response.setStatusCode(200); return response; } }
- Pressione Ctrl+S para salvar.
Na sua classe de teste, crie o método testPostCallout para configurar o callout simulado, conforme veremos no exemplo a seguir. O método testPostCallout chama Test.setMock que, por sua vez, chama o método makePostCallout na classe AnimalsCallouts. Depois, ele verifica se a resposta devolvida é o que você especificou no método de resposta da implementação simulada.
- Altere a classe de teste AnimalsCalloutsTest para adicionar um segundo método de teste. Clique na guia de classe e acrescente o seguinte método antes de fechar o parênteses.
@isTest static void testPostCallout() { // Set mock callout class Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock()); // This causes a fake response to be sent // from the class that implements HttpCalloutMock. HttpResponse response = AnimalsCallouts.makePostCallout(); // Verify that the response received contains fake values String contentType = response.getHeader('Content-Type'); System.assert(contentType == 'application/json'); String actualValue = response.getBody(); System.debug(response.getBody()); String expectedValue = '{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}'; System.assertEquals(actualValue, expectedValue); System.assertEquals(200, response.getStatusCode()); }
- Pressione Ctrl+S para salvar.
- Selecione .
- Na lista Classes de teste, selecione AnimalsCalloutsTest.
- Clique em
O resultado do teste será exibido na guia Testes com uma nova ID de execução de teste. Quando a execução do teste terminar, expanda a execução de teste para ver os detalhes de ambos.
.
Quero saber mais...
Ao fazer um callout a partir de um método, esse método espera o serviço externo enviar a resposta do callout antes de executar as próximas linhas de código. Outra opção é colocar o código do callout em um método assíncrono com uma anotação @future(callout=true) ou usar o Apex que permite a execução em fila. Dessa forma, o callout é executado em um thread separado e a execução do método de chamada não fica bloqueada.
Ao fazer um callout a partir de um acionador, o callout não deve bloquear o processo de acionamento enquanto espera por uma resposta. Para que o acionador consiga fazer um callout, o método que contém o código do callout deve ter a anotação @future(callout=true) para ser executado em um outro thread.