New release is coming soon! If you want to try out the latest features, simply run npm i -s moleculer@next. The docs for the latest version are available here.
Testes
A implementação de testes (unitários) é uma parte crucial do desenvolvimento de software, pois garante que todos os componentes de uma aplicação funcionem conforme o esperado. Esta página cobre como testar uma aplicação típica baseada em Moleculer.
Frameworks de Teste
Por favor, note que usamos o Jest para testar. No entanto, você também pode usar qualquer outro framework de teste que ofereça as mesmas capacidades.
Estrutura de arquivo comum
O trecho apresentado abaixo é o esqueleto de uma estrutura para escrever testes unitários para um serviço Moleculer.
describe("Test '<SERVICE-NAME>'", () => { // Create a service broker let broker = new ServiceBroker({ logger: false }); // Create the actual service let service = broker.createService(ServiceSchema);
// Start the broker. It will also init the service beforeAll(() => broker.start()); // Gracefully stop the broker after all tests afterAll(() => broker.stop());
/** Tests go here **/ });
Para testar o serviço, duas coisas são obrigatórias: a classe ServiceBroker e o esquema do serviço que será testado. Em seguida, o que precisa ser feito é criar uma instância do ServiceBroker e, depois disso, criar a instância atual do serviço. Então a função auxiliar beforeAll() do Jest é usada para iniciar o broker e, depois que todos os testes forem concluídos, o broker é parado com o afterAll().
Com esta configuração posicionada, estamos prontos para escrever os testes de fato.
DICA: Desative os logs, definindo logger como false durante a criação do broker, para evitar poluir o console.
Testes unitários
Ações
Simples
Uma ação típica (porém muito simplista) se parece com a apresentada abaixo:
A ação toUpperCase do serviço helper recebe um parâmetro name como entrada e, como um resultado, retorna o name maiúsculo. Essa ação também emite um evento (name.uppercase) toda vez que é chamado. Além disso, o toUpperCase tem uma validação de parâmetro, ele só aceita o parâmetro name se for uma string. Então para a ação toUpperCase há três coisas que podem ser testadas: o valor de saída que ela produz, se emite um evento e o parâmetro de validação.
// Check the result expect(result).toBe("JOHN"); });
it("should reject with a ValidationError", async () => { expect.assertions(1); try { await broker.call("helper.toUpperCase", { name: 123 }); } catch (err) { // Catch the error and see if it's a Validation Error expect(err).toBeInstanceOf(ValidationError); } });
it("should emit 'name.uppercase' event ", async () => { // Spy on context emit function jest.spyOn(Context.prototype, "emit");
// Call the action await broker.call("helper.toUpperCase", { name: "john" });
// Check if the "emit" was called expect(Context.prototype.emit).toBeCalledTimes(1); expect(Context.prototype.emit).toHaveBeenCalledWith( "name.uppercase", "john" ); }); }); });
Adaptadores DB
Algumas ações persistem os dados que elas recebem. Para testar tais ações é necessário simular (mock, em inglês) o adaptador do banco. O exemplo abaixo mostra como fazer isso:
const DbService = require("moleculer-db");
module.exports = { name: "users", // Load the DB Adapter // It will add "adapter" property to the "users" service mixins: [DbService],
actions: { create: { handler(ctx) { // Use the "adapter" to store the data returnthis.adapter.insert(ctx.params); } } } };
Testes unitários para as ações do serviço users com banco de dados
describe("Test 'users.create' action", () => { it("should create new user", async () => { // Replace adapter's insert with a mock usersService.adapter.insert = mockInsert;
// Call the action let result = await broker.call("users.create", { name: "John" });
// Check the result expect(result).toEqual({ id: 123, name: "John" }); // Check if mock was called expect(mockInsert).toBeCalledTimes(1); expect(mockInsert).toBeCalledWith({ name: "John" }); }); }); });
Eventos
Eventos são complicados para testar porque são chamados e esquecidos, ou seja, eles não retornam nenhum valor. No entanto, é possível testar o comportamento “interno” de um evento. Para esse tipo de testes a classe Service implementa uma função auxiliar chamada emitLocalEventHandler que permite chamar o manipulador de eventos diretamente.
module.exports = { name: "helper",
events: { async"helper.sum"(ctx) { // Calls the sum method returnthis.sum(ctx.params.a, ctx.params.b); } },
methods: { sum(a, b) { return a + b; } } };
Testes unitários para os eventos do serviço helper
describe("Test 'helper' events", () => { let broker = new ServiceBroker({ logger: false }); let service = broker.createService(HelperSchema); beforeAll(() => broker.start()); afterAll(() => broker.stop());
// Call the "helper.sum" handler await service.emitLocalEventHandler("helper.sum", { a: 5, b: 5 }); // Check if "sum" method was called expect(service.sum).toBeCalledTimes(1); expect(service.sum).toBeCalledWith(5, 5);
// Restore the "sum" method service.sum.mockRestore(); }); }); });
Métodos
Os métodos são funções privadas que estão disponíveis apenas no escopo do serviço. Isso significa que não é possível chamá-los de outros serviços ou usar o broker para fazer isso. Portanto, para testar um determinado método, precisamos de chamá-lo diretamente da instância de serviço que o implementa.
module.exports = { name: "helper",
methods: { sum(a, b) { return a + b; } } };
Testes unitários para os métodos do serviço helper
describe("Test 'helper' methods", () => { let broker = new ServiceBroker({ logger: false }); let service = broker.createService(HelperSchema); beforeAll(() => broker.start()); afterAll(() => broker.stop());
describe("Test 'sum' method", () => { it("should add two numbers", () => { // Make a direct call of "sum" method const result = service.sum(1, 2);
expect(result).toBe(3); }); }); });
Variáveis Locais
Assim como os métodos, variáveis locais também estão disponíveis somente no escopo do serviço. Isto significa que, para testá-los, temos de utilizar a mesma estratégia que é utilizada nos testes de método.
module.exports = { name: "helper",
/** actions, events, methods **/
created() { this.someValue = 123; } };
Testes unitários para as variáveis locais do serviço helper
describe("Test 'helper' local variables", () => { let broker = new ServiceBroker({ logger: false }); let service = broker.createService(HelperSchema); beforeAll(() => broker.start()); afterAll(() => broker.stop());
Testes de integração envolvem testes de dois (ou mais) serviços para garantir que as interações entre eles funcionem adequadamente.
Serviços
As situações em que um serviço depende de outro são muito comuns. O exemplo abaixo mostra que a ação notify do serviço users depende do serviço mail. Isso significa que para testar a ação notify, precisamos simular (mock, em inglês) a ação send do serviço mail.
describe("Test 'users' service", () => { let broker = new ServiceBroker({ logger: false }); let usersService = broker.createService(UsersSchema);
// Create a mock of "send" action const mockSend = jest.fn(() =>Promise.resolve("Fake Mail Sent")); // Replace "send" action with a mock in "mail" schema MailSchema.actions.send = mockSend; // Start the "mail" service let mailService = broker.createService(MailSchema);
describe("Test 'users.notify' action", () => { it("should notify the user", async () => { let result = await broker.call("users.notify");
expect(result).toBe("Fake Mail Sent"); // Check if mock was called expect(mockSend).toBeCalledTimes(1); }); }); });
API Gateway
A lógica que nossos serviços implementam geralmente também está disponível através do API gateway. Isto significa que também precisamos implementar testes de integração para o API gateway. O exemplo abaixo mostra como fazer isso:
Testing Frameworks
Por favor, note que para os testes do API gateway nós usamos o supertest. Mais uma vez, isto não é obrigatório e você pode usar qualquer outra ferramenta que ofereça as mesmas capacidades.