mcpField
N
nebula-logger-testing-your-code

nebula-logger-testing-your-code

github.com/jongpie测试与质量97486 次安装9 周前更新
代码仓库

关于 nebula-logger-testing-your-code

nebula-logger-testing-your-code 是面向在 Apex 或 LWC 代码中使用 Nebula Logger 的开发者的测试技能。它涵盖检查缓冲条目、验证已保存记录、控制测试中的保存行为,以及在 LWC 测试中模拟日志调用。

主要功能

  1. 01断言缓冲日志数量
  2. 02验证已保存的日志记录
  3. 03控制保存行为并清空缓冲区
  4. 04处理异步测试中的事件传递
  5. 05模拟并断言 LWC 日志调用

适用场景

  1. 01测试会写入日志的 Apex 代码
  2. 02检查已保存的消息和场景
  3. 03在测试期间阻止日志持久化
  4. 04验证 LWC 测试中的日志参数

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jongpie/nebulalogger --skill nebula-logger-testing-your-code

安装后在 Claude Code 中输入/nebula-logger-testing-your-code确认已加载。

详细介绍

测试使用 Nebula Logger 的代码

本技能适用于自己的 Apex 或 LWC 代码调用 Logger.info(...)、Logger.error(...) 等,并希望通过测试确认记录了正确内容的开发者。这里只使用 Nebula Logger 的 global API,因此无论组织安装的是解锁包还是托管包,都可以使用相同的技巧。

缓冲区模型

Nebula Logger 会在事务期间将日志条目缓冲在内存中。Logger.saveLog() 会将缓冲的条目移至保存流程(平台事件、队列任务、REST 或同步 DML)——有关保存方法的选择,请参阅 nebula-logger-instrumentation。

对于测试,缓冲区就是观察点:你可以添加条目,在调用 saveLog() 之前检查缓冲区大小,然后调用 saveLog() 并查询生成的记录,或者调用 flushBuffer() 丢弃这些条目而不持久化。这三个 API 都是 global 的。

在保存前断言缓冲区内容

Logger.getBufferSize() 会返回当前缓冲的条目数。使用它可以确认代码添加了预期的条目,而无需持久化这些条目。

@IsTest
static void it_should_log_a_warning_when_credit_check_fails() {
  Account account = new Account(Name = 'Test Account');
  insert account;

  System.Test.startTest();
  new CreditCheckService().evaluate(account.Id);
  System.Test.stopTest();

  System.Assert.areEqual(1, Logger.getBufferSize(), 'Expected exactly one entry buffered');
}

由于 getBufferSize() 在触发 saveLog() 之前运行,因此无需等待平台事件发布,断言也能通过。

保存后断言持久化记录

如果需要断言消息文本、日志级别、关联记录或场景,请保存日志并查询持久化的记录。日志管理端的全部自定义对象如下:

  • Log__c - 每个日志记录事务对应一条记录。
  • LogEntry__c - 每次调用 Logger.info(...) / .error(...) 等都会对应一条记录。
  • LogEntryTag__c - 应用标签时,LogEntry__c 与 LoggerTag__c 之间的关联对象。
  • LoggerScenario__c - 记录各事务中使用的每个唯一 Logger.setScenario(...) 值。
  • LoggerTag__c - 由 LogEntryTag__c 引用的标签目录。

使用默认的 EVENT_BUS 保存方法时,平台事件会在 System.Test.stopTest() 期间触发,并填充上述所有对象。

何时还需要 System.Test.getEventBus().deliver()

System.Test.stopTest() 只会传递在 stopTest 边界之前排队的平台事件。任何在 stopTest 自身刷新异步工作期间发布的 LogEntryEvent__e 记录,都需要额外调用 System.Test.getEventBus().deliver(),之后 LogEntry__c / Log__c 记录才会生成。以下两种情况需要注意:

  • Logger.setSaveMethod(Logger.SaveMethod.QUEUEABLE) 或 Logger.saveLog(Logger.SaveMethod.QUEUEABLE) - Nebula Logger 基于队列任务的保存会在队列任务内部发布平台事件。队列任务会在 stopTest 期间运行,但它发布的事件需要再进行一次传递。
  • 被测异步代码本身会记录日志 - 在 execute 中调用 Logger.info(...) / .error(...) / saveLog() 的 System.Queueable、System.Finalizer 或 Database.Batchable 会在异步上下文中发布平台事件。stopTest 会运行异步作业,但不会传递该作业产生的平台事件。
@IsTest
static void it_should_log_the_error_from_a_failed_queueable() {
  System.Test.startTest();
  System.enqueueJob(new ExampleFailedQueueable());
  System.Test.stopTest();
  System.Test.getEventBus().deliver();

  List<LogEntry__c> entries = [SELECT LoggingLevel__c FROM LogEntry__c WHERE LoggingLevel__c = 'ERROR'];
  System.Assert.areEqual(1, entries.size());
}

对于使用默认 EVENT_BUS 保存方法的同步代码,只调用 stopTest 就够了,无需额外调用 deliver。

@IsTest
static void it_should_log_the_error_message_when_credit_check_throws() {
  Account account = new Account(Name = 'Test Account');
  insert account;

  System.Test.startTest();
  System.Exception thrownException;
  try {
    new CreditCheckService().evaluate(account.Id);
    System.Assert.fail('Expected an exception');
  } catch (System.Exception ex) {
    thrownException = ex;
  }
  System.Test.stopTest();

  System.Assert.areNotEqual('Expected an exception', thrownException.getMessage(), 'The wrong exception was thrown!');
  List<LogEntry__c> entries = [
    SELECT LoggingLevel__c, Message__c, RecordId__c, ExceptionMessage__c, ExceptionType__c
    FROM LogEntry__c
  ];
  System.Assert.areEqual(1, entries.size());
  System.Assert.areEqual('ERROR', entries[0].LoggingLevel__c);
  System.Assert.areEqual(thrownException.getMessage(), entries[0].ExceptionMessage__c);
  System.Assert.areEqual(thrownException.getTypeName(), entries[0].ExceptionType__c);
}

控制是否持久化条目

默认情况下,Nebula Logger 会在测试上下文中遵循 LoggerSettings__c:如果组织默认设置启用了日志记录,且级别适当,条目就会持久化。如果你希望测试运行被测代码而不触及数据库:

  • 在测试开头调用 Logger.suspendSaving()。后续的 saveLog() 调用都不会执行任何操作——不会插入记录、不会发布平台事件,也不会更改缓冲区。
  • 调用 Logger.resumeSaving() 可恢复保存。保存暂停期间累积在缓冲区中的所有条目仍会保留,因此下一次调用 saveLog() 时会将它们全部刷新。如果你想丢弃这些条目而不是持久化它们,请在恢复保存前调用 Logger.flushBuffer()。
  • 如果你只想避免测试触发的条目在设置完成前累积,请在测试开头调用 Logger.flushBuffer()。
  • 编写共享测试工具时,可调用 Logger.isSavingSuspended() 来避免测试污染。
@IsTest
static void it_should_not_persist_log_records_when_saving_is_suspended() {
  Logger.suspendSaving();

  new CreditCheckService().evaluate(fakeAccountId());
  Logger.saveLog();

  System.Assert.isTrue([SELECT COUNT() FROM Log__c] == 0);
}

Nebula Logger 的 CMDT 记录在测试中也会生效

Salesforce 在测试查询 CMDT 时会返回组织中的实际自定义元数据记录——平台不会像回滚 sObject 数据那样回滚这些记录。Nebula Logger 利用了这一点:内部选择器层会读取已部署到组织中的 LoggerParameter__mdt、LogEntryDataMaskRule__mdt、LogEntryTagRule__mdt、LoggerFieldMapping__mdt、LoggerPlugin__mdt、LoggerScenarioRule__mdt、LoggerSObjectHandler__mdt 和 LogStatus__mdt 记录,并在测试中按照运行时的方式使用它们。

对订阅方自己的测试而言,实际影响如下:

  • 更改组织中的 LoggerParameter__mdt.Value__c(例如,将 QueryUserDataSynchronously 从 true 改为 false,或为预期为布尔值的参数设置非布尔值)会影响测试行为,而不只是运行时行为。依赖开箱即用值的测试可能会在配置更改后开始失败。
  • 部署的新 LogEntryDataMaskRule__mdt 记录也会在测试中屏蔽敏感数据,因此对原始消息文本的断言需要预期经过屏蔽的文本。
  • 新的 LoggerPlugin__mdt 记录也会在测试中执行。如果插件有副作用(调用外部接口、DML、平台事件),测试就需要对这些行为进行模拟或加以考虑。
  • LoggerScenario__mdt 记录在测试时会像运行时一样覆盖 LoggerSettings__c。

应将 CMDT 更改视为行为更改,并在部署对 Nebula Logger CMDT 记录的任何自定义配置后重新运行相关测试套件。Nebula Logger 内部的 LoggerConfigurationSelector.useMocks() 接口并非 global,因此订阅方测试无法使用它——受支持的做法是让测试知晓组织的 CMDT 状态,并在测试需要跳过持久化相关操作时使用 Logger.suspendSaving() / flushBuffer()。

LWC 测试注意事项

对于使用 sfdx-lwc-jest 的 LWC 测试,请模拟 c/logger 模块,并断言组件调用了正确的条目级方法,且传入了正确的消息 / 记录。

// __tests__/paymentPanel.test.js
import { createElement } from 'lwc';
import PaymentPanel from 'c/paymentPanel';

const mockLogger = {
  setScenario: jest.fn(),
  info: jest.fn().mockReturnThis(),
  error: jest.fn().mockReturnThis(),
  setExceptionDetails: jest.fn().mockReturnThis(),
  saveLog: jest.fn()
};

jest.mock('c/logger', () => ({
  getLogger: () => mockLogger
}));

describe('c-payment-panel', () => {
  it('logs an INFO entry when the panel initializes', () => {
    const element = createElement('c-payment-panel', { is: PaymentPanel });
    document.body.appendChild(element);

    expect(mockLogger.setScenario).toHaveBeenCalledWith('Payment UI');
    expect(mockLogger.info).toHaveBeenCalledWith('Payment panel initialized');
    expect(mockLogger.saveLog).toHaveBeenCalled();
  });
});

按照仓库的测试约定,应断言传递给日志记录器方法的参数(toHaveBeenCalledWith(...)),而不只是断言这些方法曾被调用。仅使用 toHaveBeenCalled() 可能会让消息文本丢失或乱码之类的回归问题漏网。

适用于测试代码的全局 API

只有 global API 支持在你自己的测试代码中使用。任何 public 内容都属于 Nebula Logger 的内部接口,可能会在不另行通知的情况下发生变化——请参阅 nebula-logger-instrumentation 中的“受支持的 API 接口”部分。

以下 API 均为 global,可在任一软件包中安全地从订阅者的测试套件调用:

  • Logger.getBufferSize() - 查看缓冲的条目数量。
  • Logger.suspendSaving() / Logger.resumeSaving() / Logger.isSavingSuspended() - 阻止或控制持久化。
  • Logger.flushBuffer() - 丢弃缓冲的条目,不进行持久化。
  • Logger.getTransactionId() - 在断言持久化记录时关联测试日志。
  • Logger.setScenario(...) / Logger.endScenario(...) - 设置测试专用的场景,便于识别持久化日志。
  • 特定级别的方法(Logger.info(...)、Logger.error(...) 等)以及 Logger.saveLog()。

相关技能

评论

支持 Markdown