单元测试
- 测试类不记录到 APEX 的 6MB 限制
- 测试方法不发送邮件
- 测试方法无法调用外部服务
- 测试中执行SOSL返回空结果,可以用 Test.setFixedSearchResults() 或者 SeeAllData=true
- 测试类中的SQL操作会自动回滚,但是不能违反主键约束,否则会报错
普通类测试
注意:默认情况下,测试类无法访问业务数据,如果存在这种情况,需要使用 SeeAllData=true
官网不建议打开 SeeAllData=true ,因为测试环境和生成环境的业务数据可能不同,会导致测试结果不同
可以在测试类中初始化业务数据,就可以查询到响应的数据了,见 testDoPost3
@RestResource(urlMapping='/account_rest_api/*')
global with sharing class AccountRestApi{
@HttpPost
global static String doPost(String Name) {
List<Account> accList = [SELECT Id,Name FROM Account WHERE Name =: Name LIMIT 1];
// 测试类需要打开 SeeAllData=true ,否则 accList.isEmpty() 永远为 true
if (accList.isEmpty()) {
return 'not exists';
}
return 'exists';
}
}
@isTest
private class AccountRestTest {
@isTest(SeeAllData=true)
static void testDoPost1() {
// 数据库存在该用户名
String response = AccountRestApi.doPost('测试啦啦啦');
System.assertEquals('exists', response);
}
@isTest(SeeAllData=true)
static void testDoPost2() {
// 数据库不存在该用户名
String response = AccountRestApi.doPost('没有的');
System.assertEquals('not exists', response);
}
@isTest
static void testDoPost3() {
Account acc = new Account(Name = '没有的');
insert acc;
// 一般准备数据写在 startTest 前,断言逻辑写在 stopTest 后
Test.startTest();
String response = AccountRestApi.doPost('没有的');
Test.stopTest();
System.assertEquals('exists', response);
}
}
trigger测试
trigger TestObjectTrigger on TestObject__c (before insert) {
for (TestObject__c tj : Trigger.new) {
if (tj.Phone__c == null) {
tj.Phone__c = '不存在';
}
}
}
对于触发器的单元测试,还是填入 SeeAllData=true 吧,会省事很多
@isTest(SeeAllData=true)
private class TestObjectTriggerTest {
@isTest
static void testBeforeInsert()
{
Test.startTest();
TestObject__c tj = new TestObject__c(Name = '🐶');
insert tj;
Test.stopTest();
TestObject__c tj1 = [SELECT Phone__c from TestObject__c WHERE Name = '🐶'];
System.assertEquals('不存在', tj1.Phone__c);
}
}
公用测试数据
如果多个测试用例中需要使用 mock 数据,可以提取到数据工厂中
@isTest
public class AccountDataFactory {
public static List<Account> createAccount(Integer row) {
List<Account> accList = new List<Account>();
for (Integer i = 0; i < row; i++) {
accList.add(new Account(Name = '狗蛋' + i));
}
insert accList;
return accList;
}
}
@isTest
private class AccountRestTest {
@isTest
static void testDoPost() {
// 从 数据工厂中获取 mock 数据
List<Account> accList = AccountDataFactory.createAccount(1);
Account acc = accList.get(0);
Test.startTest();
String response = AccountRestApi.doPost('狗蛋0');
Test.stopTest();
System.assertEquals('exists', response);
}
}
调用外部服务测试
测试类中不允许调用外部服务,需要 mock 数据来进行测试调用外部服务
public class CallOut {
public HttpResponse post()
{
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setMethod('POST');
request.setEndpoint('https://www.test.com/login');
request.setHeader('Content-Type', 'application/json;charset=UTF-8');
request.setBody('{"account":"admin","password":"123456"}');
HttpResponse response = http.send(request);
return response;
}
}
@isTest
global class CallOutMock implements HttpCalloutMock {
global HttpResponse respond(HttpRequest request)
{
HttpResponse response = new HttpResponse();
response.setHeader('Content-Type', 'application/json');
response.setBody('{"code":"1000","message":"用户名或密码错误"}');
response.setStatusCode(200);
return response;
}
}
@isTest
private class CallOutTest {
@isTest
static void testCallOutGet()
{
Test.setMock(HttpCalloutMock.class, new CallOutMock());
CallOut co = new CallOut();
HttpResponse response = co.post();
String contentType = response.getHeader('Content-Type');
System.assert( contentType == 'application/json');
String body = response.getBody();
System.debug(body);
String expectedValue = '{"code":"1000","message":"用户名或密码错误"}';
System.assertEquals(expectedValue, body);
System.assertEquals(200, response.getStatusCode());
}
}