Внедрение нового экземпляра для проверки поведения сохраняемости службы Angular (singleton)

#angular #unit-testing #dependency-injection

#angular #модульное тестирование #внедрение зависимостей

Вопрос:

У меня есть служба Angular, которая использует другую службу для сохранения некоторых своих данных между загрузками страницы:

 @Service({providedIn: "root"})
class PersistenceService {
  save(key: string, value: string) { ... }
  load(key: string): string { ... }
}

@Service({providedIn: "root"})
class DataService {
  user?: string;
  constructor(private persistence: PersistenceService) {}

  init() { this.user = this.persistence.load("user"); }

  setUser(str: string) {
    this.user = str;
    this.persistence.save("user", str);
  }
}
  

Когда я хочу протестировать DataService , я внедряю отключенный шпион для PersistenceService использования класса Angular TestBed :

 beforeEach(() => {
  fakeValues = {};
  persistence = jasmine.createSpyObj("PersistenceService", ["save", "load"]);
  persistence.load.and.callFake(key => fakeValues[key]);
  TestBed.configureTestingModule({
    providers: [
      DataService,
      {provide: PersistenceService, useValue: persistence},
    ]
  });
  instance = TestBed.inject(DataService);
})
  

Я хочу протестировать способ DataService использования сохраняемых значений. Я могу сделать это достаточно легко, манипулируя макетом PersistenceService ( fakeValues в приведенном выше примере). Например:

 it("saves the user", () => {
  instance.init();
  instance.setUser("Joe");
  expect(persistence.save).toHaveBeenCalledWith("user, "Joe");
});

it("loads the user", () => {
  fakeValues.user = "Joe";
  instance.init();
  expect(instance.user).toBe("Joe");
});
  

Я думаю, что это может быть неправильный способ сделать это, потому что для этого требуется, чтобы я знал детали реализации DataService : тот факт, что пользователь сохраняется с использованием ключа «user». На самом деле я хочу протестировать, когда я вызываю setUser("Joe") , затем перезагружаю страницу (создавая новый экземпляр DataService ), проверяю, что instance.user она извлекается из службы сохраняемости, и устанавливается значение «Joe».

В приведенном выше примере я мог бы, конечно, просто init снова вызвать метод или создать второй экземпляр напрямую new DataService(TestBed.inject(PersistenceService)) , но в моем реальном коде может быть невозможно сбросить состояние экземпляра службы и повторно инициализировать, а некоторые службы имеют 4-6 зависимостей, поэтому создание одного вручную является проблемой. Похоже, что лучшим шаблоном был бы тот, в котором я могу внедрить тестируемый сервис, внести некоторые изменения, затем имитировать перезагрузку страницы и создать совершенно новый экземпляр и проверить его поведение, и все это в одном тестовом примере. Есть ли простой способ сделать это?

Ответ №1:

Я слишком много думал об этом.

Вызов TestBed.resetTestingModule() уничтожает все экземпляры одноэлементной службы. Таким образом, я могу переместить поведение настройки из beforeEach в его собственную функцию (назовем ее setup ), затем в тесте я могу просто сделать:

 instance.init();
expect(instance.user).toBeUndefined();
instance.setUser("Joe");

// Simulate page reload
TestBed.resetTestingModule();
setup();

expect(instance.user).toBeUndefined();
instance.init();
expect(instance.user).toBe("Joe");