This app is developed with Nest.js, it is an auth app tested with Jest: : unit, integration and e2e testing
- Azure account
- Nest.js
- Jest
- Clone the repo
- Install dependencies :
npm install && jest
- Launch test
npm test
for unit and integration testing - Launch test
npm run test:e2e
for unit and End to End testing - Create Azure webapp
- Add the webapp's profile publish to github secrets
- Push to deploy
In this project we are using 3 types of tesing :
Unit testing is a type of testing to check if the small piece of code or a single function is doing as per the expectation.
- Create mock services
export const MockUserService = jest.fn().mockReturnValue({
register: jest.fn().mockResolvedValue(userStub()),
findAll: jest.fn().mockResolvedValue([userStub()]),
})
- Create Mock Model
export abstract class MockModel<T> {
protected abstract entityStub: T;
constructor(createEntityData: T) {
this.constructorSpy(createEntityData);
}
constructorSpy(_createEntityData: T): void {}
findOne(): { exec: () => T } {
return {
exec: (): T => this.entityStub
}
}
async find(): Promise<T[]> {
return [this.entityStub]
}
async create(): Promise<T> {
return this.entityStub;
}
}
- Initialize test with it dependecies : controller, service, mockModel ...
const moduleRef = await Test.createTestingModule({
imports: [],
controllers: [UserController],
providers: [UserService, {
provide: getModelToken("User"),
useClass: UserModel
}],
}).compile()
- Test every function in the controller :
- First we should test that the service is ccalled seccessfully
- Then we should test if we have the expected return value
beforeEach(async () => {
jest.spyOn(userService, 'findAll');
users = await userController.findAll()
})
test('the service should be called', () => {
expect(userService.findAll).toHaveBeenCalled();
})
test('then it should return a list of users', () => {
expect(users).toEqual([userStub()])
})
})
We used the autoMocking of the service:
jest.mock('./__mocks__/user.service')
Integration testing is the phase of software testing in which individual software modules are combined and tested as a group. It follows unit testing and precedes system testing. Integration testing takes as its input modules that have been unit tested, groups them in larger aggregates, applies tests defined in an integration test plan to those aggregates, and delivers as its output the integrated system ready for system testing.
While unit tests always take results from a single unit, such as a function call, integration tests may aggregate results from various parts and sources.
Integration testing might require acting like a consumer or user of the application by:
- Calling an HTTP REST API
- Calling an API
- Calling a web service
Unlike unit testing we don't need to mock models or services.
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule]
}).compile();
app = moduleRef.createNestApplication<NestExpressApplication>();
await app.listen(3333);
httpServer = app.getHttpServer();
dbConnection = moduleRef.get<DatabaseService>(DatabaseService).getDbHandle();
})
The database service is the one responsoble for the connection
Note that in the module we added :
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
uri: configService.get('CONNECTION_STRING'),
}),
inject: [ConfigService],
}),
Example of testing :
describe('createUser', () => {
it('should create a user', async () => {
const createUserRequest: CreateUserDto = {
name: userStub().name,
email: userStub().email,
password: userStub().password,
phoneNumber: userStub().phoneNumber
}
const response = await request(httpServer).post('/user/register').send(createUserRequest)
console.log(response.body)
expect(response.status).toBe(201);
expect(response.body).toMatchObject(createUserRequest);
const user = await dbConnection.collection('users').findOne({ email: createUserRequest.email });
expect(user.name).toEqual(createUserRequest.name);
})
})
})
Userstub contains the user that we created for test
End-to-end testing is a technique that tests the entire software product from beginning to end to ensure the application flow behaves as expected. It defines the product’s system dependencies and ensures all integrated pieces work together as expected.
Example of testing user creation:
it('should create a new user', ()=>{
return request(app.getHttpServer())
.post('/user/register')
.send({
name : "test",
email : "test@hotmail.com",
password : "12345678",
phoneNumber : "23569874"
})
.expect(201)
});
Click here to get the document
In this part we will automate the tests written in the previous chapter using github actions:
Test:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "14"
- run: npm install
- run: |
touch .env
echo CONNECTION_STRING="${{ secrets.MONGO_CONNECTION_STRING }}" >> .env
echo APP_PORT = 3000 >> .env
echo MORGAN_ENV = "dev" >> .env
- run: npm test
- run: npm run test:e2e
-
Create azure group
-
Create azure plan :
az appservice plan create --resource-group MY_RESOURCE_GROUP --name MY_APP_SERVICE_PLAN --is-linux
-
Create azure web app :
az webapp create --name MY_WEBAPP_NAME --plan MY_APP_SERVICE_PLAN --resource-group MY_RESOURCE_GROUP --runtime "NODE|14-lts"
-
Add pulish profile to Github secrets
-
Build: in the build part we need to configue node enviorement, install dependencies and run tests
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: npm install
run: npm install
- name: .env settings
run: |
touch .env
echo CONNECTION_STRING="${{ secrets.MONGO_CONNECTION_STRING }}" >> .env
echo APP_PORT = 3000 >> .env
echo MORGAN_ENV = "dev" >> .env
- name: npm build
run: npm run-script build --if-present
- Deploy : At this stage all we have to do is using the deploy artifacts already installed in the build part
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- name: 'Deploy to Azure WebApp'
id: deploy-to-webapp
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}