Se você é um desenvolvedor, eu posso afirmar com 100% de certeza que você já ouviu falar em Testes Unitários, não é mesmo?

E se você já escreveu uma classe de teste para o Salesforce, você provavelmente faz como a maioria dos desenvolvedores, utiliza o famoso Test.isRunningTest().

Se você utiliza deste artificio, a minha dica é, pare imediatamente, a menos que seja algo que seja impossível de ser testado, como o envio de email por exemplo, mas eu tenho visto muitos códigos de desenvolvedores, que ao realizar uma chamada HTTP acabam por colocar um um hack usando o Test.isRunningTest() para não fazer a chamada HTTP, e isso muitas vezes impossibilita você de consegui uma cobertura de código acima de 75%, exigida pelo Salesforce para implantação em Produção, e ainda impossibilitando você de testar efetivamente o retorno da Chamada HTTP, mas afinal, como simular então uma chamada HTTP em um Test Unitário?

A resposta é bem simples, basta implementar o HttpCalloutMock, no exemplo que vou dar a seguir, vamos implementar essa interface e também criar alguns artifícios para que você possa reutilizar esta classe em TODAS as suas chamadas HTTP, simulando por exemplo chamadas com erro 500, 404 e qualquer outro cenário que você necessite, tudo isso implementando apenas uma vez o HttpCalloutMock, e reutilizando isso em todos os seus testes.

Bom, mas chega de papo né, e vamos ao que interessa, abaixo uma implementação limpa da interface HttpCalloutMock, ela exige apenas que você implementa o método respond

@isTest
global class  HttpCalloutMockTools implements HttpCalloutMock {
   public System.HttpResponse respond(System.HttpRequest request) {
      return null;
   }
}

Bom, mas é obvio que somente isso não vai resolver o seus problemas, então vamos efetivamente colocar alguns códigos que vão fazer o respond funcionar como deveria, e deixar a classe mais dinâmica!

Primeiro, vamos adicionar algumas propriedades que serão utilizadas dentro do método respond.

private Integer responseStatusCode = 200;
private String responseBody = 'ok';
private Map<String, String> responseBodyForRequestUrl = null;
private Map<String, String> responseHeaders = null;

E em seguidas, alguns métodos para que possamos popular essas propriedades.

public void setStatusCode(Integer statusCode) {
   this.responseStatusCode = statusCode;
}

public void setResponseBody(String body) {
   this.responseBody = body;
}

public void addResponseBodyForRequest(String requestUrl, String body) {
   if (this.responseBodyForRequestUrl == null)
      this.responseBodyForRequestUrl = new Map<String, String>();

   this.responseBodyForRequestUrl.put(requestUrl, body);
}

public void addHeader(String key, String value) {
   if (this.responseHeaders == null)
      this.responseHeaders = new Map<String, String>();

   this.responseHeaders.put(key, value);
}

E por fim, implementar o método respond, para retornar o que foi definido anteriormente, assim a nossa classe completa ficará assim:

@isTest
global class HttpCalloutMockTools implements HttpCalloutMock {
   private Integer responseStatusCode = 200;
   private String responseBody = 'ok';
   private Map<String, String> responseBodyForRequestUrl = null;
   private Map<String, String> responseHeaders = null;

   public void setStatusCode(Integer statusCode) {
      this.responseStatusCode = statusCode;
   }

   public void setResponseBody(String body) {
      this.responseBody = body;
   }

   public void addResponseBodyForRequest(String requestUrl, String body) {
      if (this.responseBodyForRequestUrl == null)
         this.responseBodyForRequestUrl = new Map<String, String>();

      this.responseBodyForRequestUrl.put(requestUrl, body);
   }

   public void addHeader(String key, String value) {
      if (this.responseHeaders == null)
         this.responseHeaders = new Map<String, String>();

      this.responseHeaders.put(key, value);
   }

   public System.HttpResponse respond(System.HttpRequest request) {
      HttpResponse res = new HttpResponse();
      if (this.responseHeaders != null) {
         for (String key : this.responseHeaders.keySet()) {
            res.setHeader(key, this.responseHeaders.get(key));
         }
      }

      if (this.responseBodyForRequestUrl != null &&
            this.responseBodyForRequestUrl.containsKey(request.getEndpoint())) {
         String bodyToSend = this.responseBodyForRequestUrl.get(request.getEndpoint());
         res.setBody(bodyToSend);
      } else
         res.setBody(this.responseBody);

      res.setStatusCode(this.responseStatusCode);

      return res;
   }
}

Ok, uma vez que você já tem esse código implementado e salvo na sua ORG, agora podemos começar a fazer uso dela, mas antes, mais uma dica rápida, se você ainda é daqueles que usa o Eclipse para programar para o Salesforce, sugiro ver o meu post de como eliminar o Eclipse da sua vida de uma vez por todas.

E porque eu prefiro o IntelliJ ao invés do Eclipse? isso é muito simples, basta utilizá-lo por algum tempo e você terá uma nova perspectiva de programação, uma tecla de atalho que utilizo muito, é o auto-completar, Control + Espaço, essa tecla de atalho te listará todos os métodos disponíveis para você utilizar naquele contexto, e isso será útil para você aprender a utilizar essa sua nova classe.

Bom então vamos ao que interessa, vamos criar uma classe que irá pegar uma URL é gerar um Shortlink com ela utilizando a API do Google, essa classe inclusive pode ser útil para você em seus projetos.

public class GenerateGoogleShortlink {
   private static final GenerateGoogleShortlink instance = new GenerateGoogleShortlink();
   private static final String googleApiUrl = 'https://www.googleapis.com/urlshortener/v1/url';

   public static GenerateGoogleShortlink getInstance() {
      return instance;
   }

   public String getShortLink(String longUrl) {
      String sBody = '{"longUrl": "' + longUrl + '"}';

      HttpRequest request = new HttpRequest();
      request.setEndpoint(googleApiUrl);
      request.setMethod('POST');
      request.setHeader('Content-Type', 'application/json');
      request.setTimeout(5000);
      request.setBody(sBody);

      Http http = new Http();
      HttpResponse response = http.send(request);

      if (response.getStatusCode() == 200) {
         Map<String, Object> mapBody = (Map<String, Object>)JSON.deserializeUntyped(response.getBody());
         if (String.valueOf(mapBody.get('status')) == 'OK')
            return String.valueOf(mapBody.get('id'));
         else
            return null;
      } else
         return null;
   }
}

E por fim, a nossa classe de teste, para conseguirmos 100% de cobertura, foram realizados 3 testes, um com o cenário FELIZ onde o retorno deve ser a URL curta, e outros 2 para simular os possíveis cenários INFELIZES, o primeiro é um erro de chamada HTTP que retorne um statusCode diferente de 200 e o último com statusCode 200, porém com o Status do JSON de retorno do Google diferente de OK, veja abaixo como ficou a nossa classe de teste.

@IsTest
public class GenerateGoogleShortlinkTest {

   @IsTest
   static void generateGoogleShortlink200() {
      Test.startTest();
      HttpCalloutMockTools mock = new HttpCalloutMockTools();
      mock.setResponseBody('{"kind": "urlshortener#url","id": "http://goo.gl/fbsS","longUrl": "http://www.google.com/","status": "OK"}');

      Test.setMock(HttpCalloutMock.class, mock);
      mock.setStatusCode(200);

      String result = GenerateGoogleShortlink.getInstance().getShortLink('http://www.google.com/');
      System.assertEquals(result, 'http://goo.gl/fbsS');
      Test.stopTest();
   }

   @IsTest
   static void generateGoogleShortlink404() {
      Test.startTest();
      HttpCalloutMockTools mock = new HttpCalloutMockTools();
      mock.setResponseBody('');

      Test.setMock(HttpCalloutMock.class, mock);
      mock.setStatusCode(404);

      String result = GenerateGoogleShortlink.getInstance().getShortLink('http://www.google.com/');
      System.assertEquals(result, null);
      Test.stopTest();
   }

   @IsTest
   static void generateGoogleShortlinkErroGoogle() {
      Test.startTest();
      HttpCalloutMockTools mock = new HttpCalloutMockTools();
      mock.setResponseBody('{"kind": "urlshortener#url","id": "http://goo.gl/fbsS","longUrl": "http://www.google.com/","status": "FAIL"}');

      Test.setMock(HttpCalloutMock.class, mock);
      mock.setStatusCode(200);

      String result = GenerateGoogleShortlink.getInstance().getShortLink('http://www.google.com/');
      System.assertEquals(result, null);
      Test.stopTest();
   }
}

Espero que este post seja muito útil para você, e que você se liberte de uma vez por todas do Test.isRunningTest() nas suas chamadas HTTP, não deixe de compartilhar com seus amigos este post, caso tenha ficado com alguma dúvida, deixe o seu comentários abaixo e farei o possível para tentar esclarecer todas elas.

Um forte abraço, e até o próximo post :)

 

Fernando Sousa

Fernando Sousa

Senior Salesforce Developer

Bacharel em Sistemas da Informação pela Universidade de Taubaté (UNITAU) e MBA em Projeto de Aplicações para Dispositivos Móveis pelo IGTI – Instituto de Gestão em Tecnologia da Informação. 

Comecei a programar bem cedo, por volta de 10 anos de idade, de maneira auto-didata passei por várias linguagens.

Em 2015 me conectei a plataforma Salesforce pela primeira vez, para fazer una integração entre um Aplicativos Mobile em android e o Salesforce Platform. 

Atualmente com as certificações Salesforce Certified Platform Developer I, Salesforce Certified Platform App BuilderSalesforce Certified Platform Developer IISalesforce Administrator e Sharing and Visibility.

Acompanhe meu Trailhead aqui.