Saltar al contenido
20 de enero de 2026·Actualizado el ·14 min de lectura

Clases de test Apex: cobertura del 85 %+ sin hacer trampa

Escribe tests Apex significativos que realmente detecten bugs — TestDataFactory, tests asíncronos, mocks de callouts y los antipatrones que inflan la cobertura sin aportar valor.

← Volver

Salesforce exige un 75 % de cobertura de código para poder desplegar. Pero hay una diferencia entre cobertura y tests que detectan bugs. La mayoría de los proyectos Apex tienen mucho de lo primero y muy poco de lo segundo. Aquí tienes cómo escribir tests que realmente importan.


Requisitos previos

  • Conocimientos básicos de Apex
  • Comprensión de la estructura de triggers/clases Apex
  • Familiaridad con las operaciones DML de Salesforce

El patrón TestDataFactory

Centraliza la creación de datos de prueba en una clase de utilidad TestDataFactory en lugar de repetirla en cada método — también para los conjuntos propios de un test, como los 200 registros de un test masivo:

@isTest
public class TestDataFactory {
 
    public static Account createAccount(String name, Boolean doInsert) {
        Account acc = new Account(
            Name = name,
            Phone = '0123456789',
            BillingCountry = 'France'
        );
        if (doInsert) insert acc;
        return acc;
    }
 
    public static List<Account> createAccounts(String namePrefix, Integer count, Boolean doInsert) {
        List<Account> accounts = new List<Account>();
        for (Integer i = 0; i < count; i++) {
            accounts.add(createAccount(namePrefix + ' ' + i, false));
        }
        if (doInsert) insert accounts;
        return accounts;
    }
 
    public static List<Contact> createContacts(Id accountId, Integer count, Boolean doInsert) {
        List<Contact> contacts = new List<Contact>();
        for (Integer i = 0; i < count; i++) {
            contacts.add(new Contact(
                FirstName = 'Test',
                LastName = 'Contact ' + i,
                AccountId = accountId,
                Email = 'test' + i + '@example.com'
            ));
        }
        if (doInsert) insert contacts;
        return contacts;
    }
}

Ventajas: un único lugar que actualizar cuando cambien las reglas de validación, reutilizable en todas las clases de test, te obliga a pensar en los campos obligatorios.


@TestSetup — configuración compartida sin sobrecarga

Usa @TestSetup para crear los datos una sola vez por clase en lugar de repetir la configuración en cada método:

@isTest
private class AccountServiceTest {
 
    @TestSetup
    static void setup() {
        Account acc = TestDataFactory.createAccount('Test Corp', true);
        TestDataFactory.createContacts(acc.Id, 5, true);
    }
 
    @isTest
    static void testGetActiveContacts() {
        Account acc = [SELECT Id FROM Account LIMIT 1];
 
        Test.startTest();
        List<Contact> result = AccountService.getActiveContacts(acc.Id);
        Test.stopTest();
 
        System.assertEquals(5, result.size(), 'Should return 5 contacts');
    }
 
    @isTest
    static void testDeactivateContacts() {
        Account acc = [SELECT Id FROM Account LIMIT 1];
 
        Test.startTest();
        AccountService.deactivateContacts(acc.Id);
        Test.stopTest();
 
        List<Contact> active = [SELECT Id FROM Contact WHERE AccountId = :acc.Id AND IsActive__c = true];
        System.assertEquals(0, active.size(), 'All contacts should be deactivated');
    }
}

Importante: los datos de @TestSetup se crean una sola vez por clase. Cada método de test parte de su estado inicial: los cambios DML que un método les aplica se revierten antes del siguiente.


Probar código asíncrono con Test.startTest() / stopTest()

Test.startTest() reinicia los governor limits y fuerza a que la ejecución asíncrona (métodos future, queueable, batch) se ejecute de forma síncrona cuando se llama a Test.stopTest():

@isTest
static void testFutureMethod() {
    Account acc = TestDataFactory.createAccount('Async Test', true);
 
    Test.startTest();
    AccountService.recalculateMetricsAsync(acc.Id); // método @future
    Test.stopTest(); // Fuerza la ejecución asíncrona aquí
 
    // Ahora consulta los resultados — el @future ya se ha ejecutado
    Account updated = [SELECT Metric_Score__c FROM Account WHERE Id = :acc.Id];
    System.assertNotEquals(null, updated.Metric_Score__c, 'Score should be calculated');
}

Mockear callouts HTTP

Los tests que hacen callouts HTTP reales fallarán. Usa HttpCalloutMock:

// Clase mock
@isTest
global class SalesforceApiMock implements HttpCalloutMock {
    global HTTPResponse respond(HTTPRequest req) {
        HTTPResponse res = new HTTPResponse();
        res.setHeader('Content-Type', 'application/json');
        res.setBody('{"id": "12345", "status": "success"}');
        res.setStatusCode(200);
        return res;
    }
}
 
// Método de test
@isTest
static void testCalloutSuccess() {
    Test.setMock(HttpCalloutMock.class, new SalesforceApiMock());
 
    Test.startTest();
    String result = ExternalApiService.createRecord('Test Data');
    Test.stopTest();
 
    System.assertEquals('12345', result, 'Should return the created ID');
}

Para los escenarios de error, crea un segundo mock que devuelva 4xx/5xx y prueba también tu ruta de manejo de errores.


Escribir aserciones significativas

Las aserciones deficientes inflan la cobertura sin detectar bugs:

// ❌ Siempre pasa
System.assert(true);
// ❌ Demasiado débil — solo comprueba que hay un resultado, no su contenido
System.assertNotEquals(null, result);
 
// ✅ Significativa — detectará regresiones
System.assertEquals(3, result.size(),
    'Expected 3 records after filtering by active status');
System.assertEquals('ESCALATED', ticket.Status__c,
    'Ticket should be escalated when SLA is breached');
System.assert(result.contains(expectedId),
    'Result set must include the account we just created');

Usa siempre el tercer parámetro (el mensaje) — te indica qué falló cuando la aserción se rompe.

Desde Winter '23, la clase Assert (Assert.areEqual, Assert.isTrue, Assert.fail…) es la API recomendada para los nuevos tests; System.assertEquals sigue siendo válido, con el mismo orden de parámetros (esperado, obtenido, mensaje).


Antipatrones a evitar

  • SeeAllData=true: expone tus tests a los datos reales de la org, haciéndolos dependientes del entorno y poco fiables. Evítalo por defecto y resérvalo para los raros casos en que los datos necesarios no se pueden crear en un test. Para el catálogo de precios estándar no hace falta: Test.getStandardPricebookId() funciona sin él.
  • Insertar en cada método de test: duplica el código y ralentiza las ejecuciones (cada método tiene sus propios límites, pero recrea todos sus datos). Usa @TestSetup o TestDataFactory.
  • Probar solo el camino feliz: escribe al menos un test negativo — ¿qué ocurre con una entrada nula, campos faltantes o datos inválidos?
  • Ignorar los tests masivos: añade un test con 200 registros para verificar que tu código está correctamente "bulkificado".
@isTest
static void testBulk200Records() {
    // Volumen propio de este test, creado mediante la factory
    List<Account> accounts = TestDataFactory.createAccounts('Bulk', 200, true);
 
    Test.startTest();
    AccountService.processAll(accounts);
    Test.stopTest();
 
    System.assertEquals(200, [SELECT COUNT() FROM Account WHERE Status__c = 'Processed'],
        'Las 200 cuentas deben procesarse en una sola transacción');
}

Resumen

PrácticaPor qué importa
TestDataFactoryFuente única de verdad para los datos de test
@TestSetupEvita DML repetidos, métodos de test más limpios
Test.startTest/stopTestFuerza la ejecución asíncrona, reinicia los límites
HttpCalloutMockPrueba la lógica de callout sin llamadas externas
Aserciones significativasRealmente detecta regresiones
Test masivo (200 registros)Verifica la "bulkificación"

Recursos

Aïcha Imène DAHOUMANE
Sobre la autora
Aïcha Imène DAHOUMANE
Administradora y Desarrolladora Salesforce

Desarrolladora Salesforce con formación en infraestructura IT — construyo soluciones fiables y mantenibles con Apex, Flows, LWC y CI/CD.

CompartirCompartir en LinkedIn