Callouts REST do Apex
Objetivos de aprendizagem
Após concluir este módulo, você estará apto a:
- Fazer um callout para receber dados de um serviço externo.
- Fazer um callout para enviar dados a um serviço externo.
- Testar callouts usando callouts simulados.
Acompanhar com o Trail Together
Deseja acompanhar um especialista enquanto trabalha nesta etapa? Veja este vídeo que faz parte da série Trail Together no Trailhead Live.
(Este clipe começa na marca dos 07:54 minutos, caso você queira retroceder e ver o início da etapa novamente.)
Noções básicas de HTTP e callouts
Os callouts REST têm HTTP como base. Para compreender como os callouts funcionam, é bom compreender algumas coisas sobre HTTP. Cada solicitação de callout está associada a um método HTTP e um ponto de extremidade. O método HTTP indica qual é o tipo de ação necessária.
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.
Tabela 1. Exemplos 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
Chegou a hora de colocar seu novo conhecimento sobre HTTP em prática com alguns callouts do Apex. Este exemplo envia uma solicitação GET a um serviço da Web para receber uma lista de criaturas da floresta. O serviço envia a resposta no formato JSON. O JSON é basicamente uma sequência de caracteres, então a classe integrada JSONParser
converte esse formato em um objeto. Depois, poderemos usar esse objeto para escrever o nome de cada animal no registro de depuraçã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 em Setup (Configurações) ().
- No Developer Console, selecione Debug (Depurar) | Open Execute Anonymous Window (Abrir janela Executar no modo anônimo).
- 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 (Somente depuração) para visualizar o resultado das instruções 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
Outro caso de uso comum para os callouts HTTP é o envio de dados para um serviço. Por exemplo: quando alguém compra o álbum mais recente do Justin Bieber ou deixa um comentário no melhor vídeo de um “Gato vestido com uma fantasia de tubarão corre atrás de um pato em cima de um aspirador Roomba”, seu navegador manda uma solicitação POST para enviar esses dados. Vejamos como as informações são enviadas no Apex.
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 em Setup (Configurações) ().
- No Developer Console, selecione Debug (Depurar) | Open Execute Anonymous Window (Abrir janela Executar no modo anônimo).
- 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 (Somente 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 File (Arquivo) | Novo (New) | Apex Class (Classe do Apex).
- 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 Arquivo | Novo | Recurso estático.
- Para o nome, insira
GetAnimalResource
.
- Para o tipo MIME, selecione text/plain (texto/simples) (apesar de estarmos usando JSON).
- Clique em 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 File (Arquivo) | Novo (New) | Apex Class (Classe do Apex).
- 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 Assert.areNotEqual(null,result, 'The callout returned a null response.'); // Verify status code Assert.areEqual(200,result.getStatusCode(), 'The status code is not 200.'); // Verify content type Assert.areEqual('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'); Assert.areEqual(3, animals.size(), 'The array should only contain 3 items.'); } }
- Pressione Ctrl+S para salvar.
- Selecione Test (Teste) | Always Run Asynchronously (Sempre executar de forma assíncrona).
Se não selecionar a opção Always Run Asynchronously (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 Teste | Nova execução.
- Na lista Classes de teste, selecione AnimalsCalloutsTest.
- Clique em Adicionar itens selecionados | Executar.
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 File (Arquivo) | Novo (New) | Apex Class (Classe do Apex).
- 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 respond
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'); Assert.isTrue(contentType == 'application/json'); String actualValue = response.getBody(); System.debug(response.getBody()); String expectedValue = '{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}'; Assert.areEqual(expectedValue, actualValue); Assert.areEqual(200, response.getStatusCode()); }
- Pressione Ctrl+S para salvar.
- Selecione Teste | Nova execução.
- Na lista Classes de teste, selecione AnimalsCalloutsTest.
- Clique em Add Selected (Adicionar itens selecionados) | Run (Executar).
O resultado do teste será exibido na guia Tests (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...
Saiba mais sobre como usar os callouts nos acionadores e no Apex assíncrono, assim como sobre criar callouts assíncronos.
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.
Recursos
-
Documentos da Web do MDN: Métodos de solicitação HTTP
-
Documentos da Web do MDN: Códigos de estado de resposta HTTP
-
Guia do desenvolvedor do Apex: Como invocar callouts usando o Apex
-
Wikipédia: REST
-
W3C Note: Simple Object Access Protocol (SOAP) 1.1