Is that possible to assign a type to this (cucumber world object) - cucumberjs

Is that possible to have the completion with this world object ?
I mean...
I have a class named Application
export class Application {
constructor() {}
async run() {
// do stuff to run the app
}
get applicationDescription(): string {
return 'The best application in the world';
}
}
In my step
Given('I run my application', {timeout: 10000}, async function() {
this.app: Application = new Application();
await this.app.run(); <-- here when I write this.app. I want to see that run is a
accessible method
});

In the most basic sense, this is world within a step definition (you must NOT use arrow functions, as you have shown):
Given('I run my application', {timeout: 10000}, async function() {
this.app = new Application();
await this.app.run();
});
When done in this fashion you are just monkey patching properties onto the world object, so its working on the basis that its type is any, so you don't get type safety/autocomplete in your IDE.
You can use a custom world to get those benefits:
class MyWorld extends World {
app: Application
constructor(props: IWorldOptions) {
super(props)
}
}
setWorldConstructor(MyWorld)
Given('I run my application', this: MyWorld, {timeout: 10000}, async function() {
this.app = new Application();
await this.app.run();
});
Note that the second param is this: MyWorld, which is allowed in typescript to provide a type override for the this. It must come before any other params. If you omit it, this is still typed as Cucumbers World so you're back to any.

Related

Cypress cy.wait(...) response body is undefined despite setting up fixture in intercept command

I have created Cypress e2e tests that use the following functions:
to mock the responses
export function getUserAndSupplier(): void {
cy.intercept('GET', `${Cypress.env('BaseUrl')}/users/me`,
{
fixture: 'shared/Users/me.json',
})
.as('users');
cy.intercept('GET', `${Cypress.env('BaseUrl')}/users/me/supplier`,
{
fixture: 'shared/Suppliers/supplier.json',
})
.as('supplier');
}
to check if responses are in accordance to the fixtures:
export function checkUserAndSupplier(): void {
cy.wait('#users')
.its('response.body')
.should('not.be.undefined')
.then((interception: any) => {
//assertions on each field
});
cy.wait('#supplier')
.its('response.body')
.should('not.be.undefined')
.then((interception: GetCurrentSupplierResponse) => {
//assertions on each field
});
}
Tests have Cucumber preprocessor implemented, the GIVEN and WHEN steps definition for given test are:
beforeEach(() => {
// intercept user and supplier api
getUserAndSupplier();
// intercept GET /paymentProviders
interceptPaymentProviders();
});
Given('User navigates to the {string} page', () => {
cy.visit('/sell/payment-providers');
// assert api calls on user and supplier
checkUserAndSupplier();
});
When('User clicks on {string} button', () => {
getActivationButton()
.scrollIntoView()
.contains('Activate')
.should('be.visible')
.and('not.be.disabled')
.click();
// Ensure Continue Button is disabled
getContinueButton()
.should('be.visible')
.and('be.disabled');
});
while the .feature file test is:
Scenario: Happy path - activate payment method
Given User navigates to the "sell/payment-providers" page
When User clicks on "activate" button
Then User is able to successfully activate payment provider
The problem is that sometimes, despite having responses mocked using fixtures (they're not null or empty), 'response.body' property is undefined, which makes tests flaky.
At the beginning I thought I have some asynchronous functions that lack await and make response.body being undefined, but this was not the case.
What may be the cause of this? And what makes it sometimes working, and sometimes not?
It's hard to tell what exactly is going on, the code looks ok.
Here's some general tips to try.
Cache
It's possible the browser cache is interfering with the intercept. To avoid caching, add this
beforeEach(() => {
Cypress.automation('remote:debugger:protocol', {
command: 'Network.clearBrowserCache'
})
...
})
Debug call sequence
To debug the network calls, combine the two intercepts and use callbacks to console.log what gets intercepted.
If something is changing the order of response, the problem may be caused by the sequence of cy.wait('#users') followed by cy.wait('#supplier') so combining the intercepts will catch that.
export function getUserAndSupplier(): void {
cy.intercept('/users*', (req) => {
if (req.url.endsWith('/me')) {
console.log('users request', req)
req.alias = 'users'
req.reply({fixture: 'shared/Users/me.json'})
}
if (req.url.endsWith('/me/supplier')) {
console.log('supplier request', req)
req.alias = 'supplier'
req.reply({fixture: 'shared/Suppliers/supplier.json'})
}
})
}
Or use a single alias for both paths and check inside the interception.
export function getUserAndSupplier(): void {
cy.intercept('/users*', (req) => {
if (req.url.endsWith('/me')) {
req.reply({fixture: 'shared/Users/me.json'})
}
if (req.url.endsWith('/me/supplier')) {
req.reply({fixture: 'shared/Suppliers/supplier.json'})
}
})
.as('both')
}
export function checkUserAndSupplier(): void {
const checkInterception = (interception) => {
if (interception.request.url.endsWith('/me')) {
console.log('users response', interception.response)
// assertions for users
}
if (interception.request.url.endsWith('/me/supplier')) {
console.log('supplier response', interception.response)
// assertions for supplier
}
}
cy.wait('#both').then(checkInterception); // first interception
cy.wait('#both').then(checkInterception); // second interception
}
Lastly, something in interceptPaymentProviders() is interfering with the other intercepts.

How to use enhancers (pipes, guards, interceptors, etc) with Nestjs Standalone app

The Nestjs module system is great, but I'm struggling to figure out how to take full advantage of it in a Serverless setting.
I like the approach of writing my domain logic in *.service.ts files, while using *.controller.ts files to take care of non-business related tasks such as validating an HTTP request body and converting to a DTO before invoking methods in a service.
I found the section on Serverless in the nestjs docs and determined that for my specific use-case, I need to use the "standalone application feature".
I created a sample nestjs app here to illustrate my problem.
The sample app has a simple add() function to add two numbers. I use class-validator for validation on the AddDto class.
// add.dto.ts
import { IsNumber } from 'class-validator'
export class AddDto {
#IsNumber()
public a: number;
#IsNumber()
public b: number;
}
And then, via some Nestjs magic, I am able to get built-in validation using the AddDto inside my controller by doing the following:
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Use `ValidationPipe()` for auto-validation in controllers
app.useGlobalPipes(
new ValidationPipe({ transform: true })
)
await app.listen(3000);
}
// app.controller.ts
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Post('add')
add(#Body() dto: AddDto): number {
// Request body gets auto validated and converted
// to an instance of `AddDto`, sweet!
return this.appService.add(dto.a, dto.b);
}
}
// app.service.ts
#Injectable()
export class AppService {
add(a: number, b: number): number {
return a + b
}
}
So far, so good. The problem now arises when using this in AWS with a Lambda function, namely:
I want to re-use the business logic in app.service.ts
I want to re-use built in validation that happens when making an HTTP request to the app, such as in the example above.
I want to use the standalone app feature so I don't have to spin up an entire nest server in Lambda
The docs hint on this being a problem:
Be aware that NestFactory.createApplicationContext does not wrap controller methods with enhancers (guard, interceptors, etc.). For this, you must use the NestFactory.create method.
For example, I have a lambda that receives messages from AWS EventBridge. Here's a snippet from the sample app:
// standalone-app.ts
interface IAddCommand {
a: number;
b: number;
}
export const handler = async (
event: EventBridgeEvent<'AddCommand', IAddCommand>,
context: any
) => {
const appContext = await NestFactory.createApplicationContext(AppModule);
const appService = appContext.get(AppService);
const { a, b } = event.detail;
const sum = appService.add(a, b)
// do work on `sum`, like cache the result, etc...
return sum
};
// lambda-handler.js
const { handler } = require('./dist/standalone-app')
handler({
detail: {
a: "1", // is a string, should be a number
b: "2" // is a string, should be a number
}
})
.then(console.log) // <--- prints out "12" ("1" + "2") instead of "3" (1 + 2)
I don't get "free" validation of the event's payload in event.detail like I do with #Body() dto: AddDto when making a HTTP POST request to /add. Preferentially, the code would throw a validation error in the above example. Instead, I get an answer of "12" -- a false positive.
Hopefully, this illustrates the crux of my problem. I still want to validate the payload of the event before calling appService.add(a, b), but I don't want to write custom validation logic that already exists on the controller in app.controller.ts.
Ideas? Anyone else run into this before?
It occurred to me while writing this behemoth of a question that I can simply use class-validator and class-transformer in my Lambda handler.
import { validateOrReject } from 'class-validator'
import { plainToClass } from 'class-transformer'
import { AddDto } from 'src/dto/add.dto'
export const handler = async (event: any, context: any) => {
const appContext = await NestFactory.createApplicationContext(AppModule);
const appService = appContext.get(AppService);
const data = getPayloadFromEvent(event)
// Convert raw data to a DTO
const dto: AddDto = plainToClass(AddDto, data)
// Validate it!
await validateOrReject(dto)
const sum = appService.add(dto.a, dto.b)
// do work on `sum`...
}
It's not as "free" as using app.useGlobalPipes(new ValidationPipe()), but only involves a few extra lines of code.
It worked for me with the following lambda file for nestjs.
import { configure as serverlessExpress } from '#vendia/serverless-express';
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '#nestjs/common';
let cachedServer;
export const handler = async (event, context) => {
if (!cachedServer) {
const nestApp = await NestFactory.create(AppModule);
await nestApp.useGlobalPipes(new ValidationPipe());
await nestApp.init();
cachedServer = serverlessExpress({
app: nestApp.getHttpAdapter().getInstance(),
});
}
return cachedServer(event, context);
};

how to use sinon to test two await in a line

I want to stub some codes that have two await in line.
import { githubApi } from "../../githubApi";
export async function getMembers (projectName) {
const members = await (await githubApi(projectName)).projects
.members({
id: 'xxx'
});
return members;
}
how can I use sinon to mock await (await githubApi(projectName)).projects
.member ? thanks
Although the API in the example seems difficult to test I will assume that has been taken into account and that githubApi is in some why mock-able.
let membersFake = sinon.fake().resolves([member1, member2, member3]);
let githubApiFake = sinon.fake().resolves({
projects: {
members: membersFake
}
});
Then you can replace the githubApi with the fake. Now if githubApi is not easily mock-able and you don't own that file maybe it is worth looking into some dependency injection.
export async function getMembers (projectName, githubApi = githubApi) {
...
}
In this way you can pass in your fake directly when testing the function in a unit test but in your app code it will default to the real one imported.

Testing an Async function using Jest / Enzyme

Trying run a test case for the following:
async getParents() {
const { user, services, FirmId } = this.props;
let types = await Models.getAccounts({ user, services, firmId: FirmId });
let temp = types.map((type) => {
if(this.state.Parent_UID && this.state.Parent_UID.value === type.Account_UID) {
this.setState({Parent_UID: {label: type.AccountName, value: type.Account_UID}})
}
return {
label: type.AccountName,
value: type.Account_UID,
}
})
this.setState({ParentOptions: temp});
}
here is what i have so far for my test:
beforeEach(() => wrapper = mount(<MemoryRouter keyLength={0}><AccountForm {...baseProps} /></MemoryRouter>));
it('Test getParents function ',async() => {
wrapper.setProps({
user:{},
services:[],
FirmId:{},
})
wrapper.find('AccountForm').setState({
SourceOptions:[[]],
Parent_UID: [{
label:[],
value:[],
}],
});
wrapper.update();
await
expect(wrapper.find('AccountForm').instance().getParents()).toBeDefined()
});
If i try to make this ToEqual() it expects a promise and not anobject, what else could I add into this test to work properly.
Goal: Make sure the functions gets called correctly. The test is passing at the moment and has a slight increase on test coverage.
Using Jest and Enzyme for React Js
you can put the await before the async method, like:
await wrapper.find('AccountForm').instance().getParents()
and compare if the state was changed.
In another way, if can mock your API request, because this is a test, then you do not need the correct API, but know if the function calls the API correctly and if the return handling is correct.
And, you cand spy the function like:
const spy = jest.spyOn(wrapper.find('AccountForm').instance(), 'getParents');
and campare if the function was called if they are triggered by some action:
expect(spy).toBeCalled()

Testing a method that is subscribed to an observable - Angular 2

I want to test a method inside of an Angular 2 component that is subscribed to an observable that is returned from a method in a service. Here is the code for the service method in summary:
public create(user: User): Observable<any> {
return this.http.post(this._api.create,
JSON.stringify(user), {
headers: this.apiConfig.getApiHeaders()
}).map((res: Response) => res.json());
}
It's easy to unit test this method because it returns an observable so I can just subscribe to it. But I want to test the method in the component that is already subscribed to this:
public onSubmit(user: User): void {
this._authentication.create(user).subscribe((token) => {
localStorage.setItem('token', token);
this.router.navigate(['/Home']);
});
}
Heres my spec so far but when I try to spyOn the localStorage.setItem it comes back as not being called. My understanding is it's probably checking to see if it's been called before it's actually been called.
it('Should login a user and on success store a token in localStorage',
injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(Login).then((fixture) => {
let instance = fixture.debugElement.componentInstance;
localStorage.clear();
spyOn(localStorage, 'setItem');
instance.onSubmit({userId: 'some#email.com', password: 'password', siteName: 'sample'});
expect(localStorage.setItem).toHaveBeenCalled();
});
})
);
I'm wondering if I need to mock out the this._authentication.create method to return a new observable with a mock response in it?
After more research a few articles indicated that I do need to mock out the service and return an Observable.of() which runs synchronously to solve the problem, ill copy the code below. This however still doesn't work, I've been working on this most of the day, I don't feel this should be that hard, any help appreciated.
class MockAuthentication extends Authentication {
public create(user: Object): Observable<any> {
return Observable.of({'test': 'test'});
}
}
Ok so it's taken me most of the day but I finally cracked it. Instead of using the injectAsync and TestComponentBuilder to set up the spec I just need to use inject and inject the component in just like you do a service. This seems fine because I don't need to test anything in the view like events.
Heres the final spec that does work:
it('Should set token in localStorage, set the new user,
and navigate to home page on succesful login',
inject([Login], (login) => {
login.router.config([ { path: '/', name: 'Home', component: Home }]);
spyOn(localStorage, 'setItem');
spyOn(login._currentUser, 'set');
spyOn(login.router, 'navigate');
login.onSubmit({ userId: 'some#email.com', password: 'password', siteName: 'sample' });
expect(localStorage.setItem).toHaveBeenCalledWith('token', 'newToken');
expect(login._currentUser.set).toHaveBeenCalledWith({ 'test': 'one' });
expect(login.router.navigate).toHaveBeenCalledWith(['/Home']);
}));
Hope this might help someone in the future.
I guess you want to inject a mock Router instance to your component and then after navigate(['/Home']) was called on the mock Router, you check if localStorage.setItem(...) was called.
See my gist here.
Basically you can do several things here. First of all, stub your http call (I'm guessing from a service) with a simple observable response of the token (or other response) you want.
service.stub.ts
export class MyStub {
public create(user: User): Observable<User> {
return Observable.of('insert test token here');
}
// other stubbed methods ...
}
And then inside your test:
myComp.spec.ts
let comp: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let sst: ServiceStub;
describe('MyComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(OnboardFacilityNewComponent, {
set: {
providers: [
{ provide: MyService, useClass: ServiceStub },
]
}
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(MyComponent);
comp = fixture.componentInstance;
st = fixture.debugElement.injector.get(MyService);
});
}));
it('should submit new onboardFacility', fakeAsync(() => {
const sst = spyOn(sst, 'create').and.returnValue(
Observable.of('some token here')
);
comp.onSubmit(testUser);
fixture.detectChanges();
expect(comp.token).toEqual('some token here');
expect(spy.calls.any()).toEqual(true);
}));
});
Here, you can simply replace actual data with test data to test the behavior of your testing, rather then your testbed, your services, localStorage, etc. Obviously the test I wrote here assumes you would store the token returned from your service in your component, rather then localStorage (though there is a way to do that), but I'm just simply to show the concept rather then your specific use case.
In your use case you'll also need to the stub the router, which you can learn how to do here.

Resources