jeudi 24 décembre 2020

NestJS E2E tests don't clean up main database connection after closing

Goal

When a NestJS E2E test stops, do not display the following message:

Jest did not exit one second after the test run has completed.

This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

Question

How can I make sure that both the Main Database Connection and the E2E Database connection are both closed after an E2E test run?

I've searched other similar StackOverflow questions and I couldn't find an answer that solved my problem, hence I opened my own question.

See the analysis below for how I came to the conclusion that answering this question will fulfill my goal.

Cause Analysis

I've done some experimentation and I believe I've narrowed down the cause, but I'm unsure as to how to fix it. I believe this is caused by NestJS not closing the main database connection in a NestJS E2E Test.

Project Setup

Here's my Database module setup code:

export const getConfig = () => TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => {
    console.log('starting database...', configService.get<number>('db.port'));
    return ({
      type: 'mysql',
      host: configService.get<string>('db.host'),
      port: configService.get<number>('db.port'),
      username: configService.get<string>('db.username'),
      password: configService.get<string>('db.password'),
      database: configService.get<string>('db.database'),
      autoLoadEntities: true,
      namingStrategy: new SnakeNamingStrategy(),
    });
  },
  inject: [ConfigService],
});

app.module.ts:

import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';

import { getConfig as getAppConfig } from './config/module-config';
import { getConfig as getDatabaseConfig } from './database/module-config';
import { HealthController } from './health/health.controller';

@Module({
  imports: [
    getAppConfig(),
    TerminusModule,
    getDatabaseConfig(),
  ],
  controllers: [HealthController],
})
export class AppModule {}

main.ts

import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import * as helmet from 'helmet';
import { AppModule } from './app.module';

/**
 * Applies common app config to both the main application and e2e test app config.
 * It's important that both the main app and the e2e apps are configured the same
 * @param app Nest application to apply configuration to
 */
export const applyAppConfig = (app : INestApplication) => {
  app.setGlobalPrefix('api');
  app.use(helmet());
};

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  applyAppConfig(app);
  app.enableShutdownHooks();

  const port = process.env.PORT || 0;
  if (process.env.NODE_ENV !== 'test') {
    await app.listen(port);
  }

  console.info(`App running on: localhost:${port}`);
}
bootstrap();

A utils.ts file that hosts some functions used in setting up e2e tests:

export const getTestConfig = () =>
  ConfigModule.forRoot({
    isGlobal: true,
    load: [testConfiguration],
  });

/**
 * Creates a test app for e2e tests that has the same configuration as the main application
 * @param {TestingModule} moduleRef A list of testing modules to inject into the app, so each e2e test injects only the dependencies it needs to function
 * @returns {INestApplication} A fully initialized INestApplication
 */
export const createTestApp = async (moduleRef : TestingModule) : Promise<INestApplication> => {
  const app = moduleRef.createNestApplication();
  applyAppConfig(app);
  return await app.init();
};

And my abbreviated app.e2e-spec.ts:

describe('AppController', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [
        getTestConfig(),
        AppModule
      ],
    }).compile();

    app = await createTestApp(moduleRef);
  });

  afterAll(async () => {
    await app.close();
  });

  describe('GET /api/health', () => {
    it('returns 200', () => {
      return request(app.getHttpServer())
        .get('/api/health')
        .expect(200);
    });
  });

// ... rest of the code excluded

Running the tests console output

Note: 12360 is my main database and 12361 is my E2E database

[Nest] 6017   - 12/24/2020, 12:39:52 PM   [NestFactory] Starting Nest application...
  console.log
    starting database... 12360

      at InstanceWrapper.useFactory [as metatype] (src/database/module-config.ts:8:13)
          at async Promise.all (index 3)

  console.log
    starting database... 12361

      at InstanceWrapper.useFactory [as metatype] (src/database/module-config.ts:8:13)
          at async Promise.all (index 3)

  console.info
    App running on: localhost:3200

      at bootstrap (src/main.ts:41:11)

 PASS  test/app.e2e-spec.ts
  AppController
    GET /api/health
      ✓ returns 200 (23 ms)
    GET /api/restdocs/
      ✓ returns 200 (3 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.637 s, estimated 4 s
Ran all test suites.
Jest did not exit one second after the test run has completed.

This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

Test completed Connections State

Checking the MySQL connections shows that the e2e tests are not correctly terminating the main database connection. This can be shown by the fact that the main database has an extra connection with user test (the user the application currently connects with). The other four connections are the MySQL Workbench connections (made with user root).

Main Database Connections

enter image description here

Test Database Connections

enter image description here

Maven cant compile test-classes when using test scope

I am currently trying to use MockBukkit for testing my Minecraft Plugin and included it in my pom with the test scope because I will only need it for that. The problem is however that when using the test scope, the dependency appears in my local repository but cannot be resolved by eclipse or Maven.

The problem appears to be that the default-compile goal from the compiler plugin tries to compile the test classes even though it cannot use the dependencies as they are only available during the testing phase. (However that is only my guess).

Maven output:

[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ CityCraft ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 32 source files to C:\Users\lptoa\Desktop\MC Plugins\CityCraft\target\classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /C:/Users/lptoa/Desktop/MC Plugins/CityCraft/src/test/java/skyvide/de/city/CityTests.java:[3,32] package be.seeseemelk.mockbukkit does not exist
[ERROR] /C:/Users/lptoa/Desktop/MC Plugins/CityCraft/src/test/java/skyvide/de/city/CityTests.java:[4,32] package be.seeseemelk.mockbukkit does not exist
[ERROR] /C:/Users/lptoa/Desktop/MC Plugins/CityCraft/src/test/java/skyvide/de/city/CityTests.java:[9,17] cannot find symbol
  symbol:   class ServerMock
  location: class test.java.skyvide.de.city.CityTests
[ERROR] /C:/Users/lptoa/Desktop/MC Plugins/CityCraft/src/test/java/skyvide/de/city/CityTests.java:[12,10] cannot find symbol
  symbol:   class Before
  location: class test.java.skyvide.de.city.CityTests
[ERROR] /C:/Users/lptoa/Desktop/MC Plugins/CityCraft/src/test/java/skyvide/de/city/CityTests.java:[19,10] cannot find symbol
  symbol:   class After
  location: class test.java.skyvide.de.city.CityTests
[ERROR] /C:/Users/lptoa/Desktop/MC Plugins/CityCraft/src/test/java/skyvide/de/city/CityTests.java:[15,22] cannot find symbol
  symbol:   variable MockBukkit
  location: class test.java.skyvide.de.city.CityTests
[ERROR] /C:/Users/lptoa/Desktop/MC Plugins/CityCraft/src/test/java/skyvide/de/city/CityTests.java:[16,34] cannot find symbol
  symbol:   variable MockBukkit
  location: class test.java.skyvide.de.city.CityTests
[ERROR] /C:/Users/lptoa/Desktop/MC Plugins/CityCraft/src/test/java/skyvide/de/city/CityTests.java:[22,13] cannot find symbol
  symbol:   variable MockBukkit
  location: class test.java.skyvide.de.city.CityTests

Location:

->src
 --->test
    --->java
      --->example
        --->ExampleTest.java

Code:

package test.java.example;

import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import main.plugin;

public class ExampleTest {
    
    private ServerMock server;
    private Plugin plugin;

    @Before
    public void setUp()
    {
        server = MockBukkit.mock();
        plugin = (Plugin) MockBukkit.load(Plugin.class);
    }

    @After
    public void tearDown()
    {
        MockBukkit.unmock();
    }
}

POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>sv-plugins</groupId>
  <artifactId>CityCraft</artifactId>
  <version>1.0</version>
  <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <profiles>
    <profile>
        <id>testing</id>
        <activation>
            <property>
                <name>test.output.dir</name>
            </property>
        </activation>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>2.3.1</version>
                    <configuration>
                      <outputDirectory>${test.output.dir}</outputDirectory>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
  </profiles>
  <repositories>
    <repository>
        <id>papermc</id>
        <url>https://papermc.io/repo/repository/maven-public/</url>
    </repository>
    <repository>
        <id>spigot-repo</id>
        <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
    </repository>
    <repository>
        <id>everything</id>
        <url>https://repo.citizensnpcs.co/</url>
    </repository>
    <repository>
        <id>skyvide</id>
        <url>https://play.skyvide.de:9443/repository/skyvide/</url>
    </repository>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
    <repository>
        <id>aikar</id>
        <url>https://repo.aikar.co/content/groups/aikar/</url>
    </repository>
  </repositories>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.0</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
      <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.3</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>shade</goal>
                </goals>
                <configuration>
                    <artifactSet>
                        <includes>
                            <include>com.github.WesJD.AnvilGUI:anvilgui</include>
                            <include>co.aikar:acf-paper</include>
                        </includes>
                    </artifactSet>
                </configuration>
            </execution>
        </executions>
      </plugin>
    </plugins>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
    </resources>
  </build>
  <dependencies>
  <dependency>
    <groupId>com.destroystokyo.paper</groupId>
    <artifactId>paper-api</artifactId>
    <version>1.15.2-R0.1-SNAPSHOT</version>
    <scope>provided</scope>
 </dependency>
  <dependency>
    <groupId>net.citizensnpcs</groupId>
    <artifactId>citizens</artifactId>
    <version>2.0.27-SNAPSHOT</version>
    <type>pom</type>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>sv-plugins</groupId>
    <artifactId>MySQL</artifactId>
    <version>1.0</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>sv-plugins</groupId>
    <artifactId>Clouds</artifactId>
    <version>1.0</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>com.github.WesJD.AnvilGUI</groupId>
    <artifactId>anvilgui</artifactId>
    <version>master-SNAPSHOT</version>
  </dependency>
  <dependency>
    <groupId>co.aikar</groupId>
    <artifactId>acf-paper</artifactId>
    <version>0.5.0-SNAPSHOT</version>
  </dependency>
  </dependencies>
</project>

is Data Driven Testing possible in GraphQL via PostMan

query myQuery{ TradingHours(filter: { Product: { eq: "1COVID" } }) { date data {

        StartContinuosTrading
        EndOpeningAuction
        EndContinuosTrading
        EndClosingAuction
        
    }
}

}

Here "1COV" is one Product. I would need to submit multiple 'POST' request with multiple product inputs say 10 products like 1COVID. I wish to use Runner in postman where product input will be from excel.

How is this possible, any help please?

Alert pop up handle by selenium

Heading

In an interview, the interviewer asks me that, we have two pages login page and home page, when we click on the login button by providing UN and PASS we go to the home page, but on the home page, we get 'Alert' pop up where we have to provide UN and Pass, How to handle this type of pop up?

-here we can't pass the UN and PASS through URL

:class Unexpected token issue in testcafe in vue app

I am trying to run test integration in Vue app and I am importing component in components.test.js but it gives an Unexpected token error

components.test.js

import flushPromises from "flush-promises";
import {Selector} from "testcafe";
import sample_vue from "../src/components/sample-vue.vue";

enter image description here

anyone can suggest?

Mock class method that is called within another function

I want to mock a class method's output, which is called by a function defined in a different module. For example:

class_module.py

class my_class:
    def __init__:
        self.a = 1

    def my_method(self):
         return [1,2,3]

function_module.py

from class_module import my_class

def my_fun():
    foo = my_class()
    return foo.my_method()

test.py

@patch("class_module.my_class.my_method")
def test_my_fun(method_mock):
    method_mock.return_value = []
    bar = my_fun()

    assert bar == []

However, I am getting an AssertionError saying that [1,2,3] != []. So I guess the mock is never happening on the method I want. Can somebody explain how to do it? And why is this happening?

PS: I have also tried patching as follows:

@patch("function_module.my_class.my_method")

Untraceable Boost.Test framework internal error: unknown reason

I'm using Boost 1.69 when I create a precondition like the following:

struct is_binary_available
        {
            std::string binary_name;

            is_binary_available(std::string&& _binary_name): binary_name(_binary_name) {}

            boost::test_tools::assertion_result operator()(boost::unit_test::test_unit_id)
            {
                return boost::test_tools::assertion_result(!boost::process::search_path(binary_name).empty());
            }
        };

and use it in a certain test case:

BOOST_AUTO_TEST_CASE(FooTest, * (boost::unit_test::timeout(20), boost::unit_test::precondition(sense::test::is_binary_available("python3"))))

the test is failing with the following:

Running 1 test case...
Entering test module "some_test"
../src/myfile.cpp(10): Entering test suite "some_test"
Boost.Test framework internal error: unknown reason

Anyone ever encountered such an error?

Many thanks :)