Testing ngrx Effects with Jasmine spy - jasmine

I am writing an ngrx effect and trying to test it. However, the effect calls a service that calls an API that will require authentication. As a result, I am trying to create a spy in Jasmine to handle returning the data. This is my first time using ngrx effects, so I am really unsure where to put different parts of the code. Nothing I have done is allowing this test to run correctly.
The effect is a very simple one as follows:
#Effect() itemSelected: Observable<Action> = this.d.pessimisticUpdate('ITEM_SELECTED', {
run: (action: ItemSelected) => {
return this.myService.getItemById(action.payload).map((res) => ({
type: 'ITEM_INFO_RETURNED',
payload: res
}));
},
onError: (a: ItemSelected, error) => {
console.error('Error', error);
}
});
constructor(private d: DataPersistence<ItemState>, private myService: MyService) {
// add auth headers here
}
My test is currently written as follows:
describe('ItemEffects', () => {
let actions: Observable<any>;
let effects: ItemEffects;
let myService = jasmine.createSpyObj('MyService', ['getItemById']);
let item1: Item = {id: 1, name: 'Item 1'};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({}),
],
providers: [
ItemEffects,
DataPersistence,
provideMockActions(() => actions),
{
provide: MyService,
useValue: myService
}
],
});
effects = TestBed.get(ItemEffects);
});
describe('someEffect', () => {
it('should work', async () => {
myService.getItemById.and.callFake(function (id) {
return items.find((r) => r.id === id);
});
actions = hot('-a-|', { a:{ type:'ITEM_INFO_RETURNED', payload:1}});
expect(effects.itemSelected).toEqual(
{ type: 'ITEM_INFO_RETURNED', payload: { item1 } }
);
});
});
});
This is still attempting to use the production MyService (requiring authentication). If I move the myService override out of the provider and into the actual test,
TestBed.overrideProvider(MyService, { useValue: myService });
I get an error that it cannot read the property "itemSelected" of undefined, which would be when I am calling the effects at the very end of the test.
I am really new to ngrx, as well as to TestBed. Is there somewhere else I should be defining this Jasmine spy? Should I be using something other than createSpyOn for this?
Thanks in advance!

Related

Why is ValidateNested fields not working in e2e tests?

I have the following NestJS controller:
class PhoneTestDto {
#IsNotEmpty()
#IsPhoneNumber()
phone: string
}
class TestDto {
#IsNotEmpty()
#Type(() => PhoneTestDto)
#ValidateNested({ always: true, each: true })
#ArrayNotEmpty()
phones: PhoneTestDto[]
}
#Controller('v1/user')
export class UserController {
constructor(private readonly userService: UserService) {}
#Post()
async addUser(#Body() body: TestDto): Promise<LoginResponseDto> {
console.log("valid")
return
}
}
When I run the app and send through Postman a request such as:
{
"phones": [{}]
}
I get a correct response (phones.0.phone must be a valid phone number...)
When I try to run an e2e test, it passes without validating the phone.
This is my test file:
describe('Test', () => {
let app: INestApplication
beforeEach(async () => {
jest.resetModules()
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile()
app = moduleRef.createNestApplication()
app.useGlobalPipes(new ValidationPipe({transform: true}))
await app.init()
})
it('test1', async () => {
const response = await request(app.getHttpServer())
.post('/v1/user')
.send({phones: [{}]})
expect(response['res']['statusCode']).toEqual(201)
})
})
Sending {} or {[]} does produce a validation error so validation is generally working, but not the validation of nested fields in the context of testing.
ALSO: Removing jest.resetModules() brings correct behavior.
I'm not sure how jest.resetModules() relate to nestjs validation, and how should I use if I do need to reset modules.

Angular jasmine how to spy an async spy method

I am new to writing test with async/await in angular.
I have the following code. The service method is an async method. The test fails saying component.options.length is 0.
Can anyone please help me how to fix the error so the options has got the value i set in spy?
Thanks
spec.ts
spySideNavService = jasmine.createSpyObj('SideNavService', [], {
setOrgUserDetails: () => {},
loadMenus: () =>
[
{
id: 'my-menu',
label: 'My Menu',
icon: 'far fa-envelope fa-2x',
url: 'url'
}
] as NavOption[]
});
describe('ngOnInit', () => {
it('should add navigation options', () => {
expect(component.options.length).toBeGreaterThan(0);
});
});
component:
ngOnInit(): void {
this.options = await this.sideNavService.loadMenus();
}
SideNavService:
async loadMenus(): Promise<NavOption[]> {
//logic
}
Tried answer given below but still not working:
describe('ngOnInit', () => {
it('should add navigation options', fakeAsync(() => {
// !! call tick(); to tell the test to resolve all promises
// before coming to my expect line
tick();
expect(component.options.length).toBeGreaterThan(0);
}));
});
You need to use fakeAsync/tick to control promises.
// !! add fakeAsync
it('should add navigation options', fakeAsync(() => {
// !! call tick(); to tell the test to resolve all promises
// before coming to my expect line
// !! call ngOnInit
component.ngOnInit();
console.log(component.options);
tick();
console.log(component.options);
expect(component.options.length).toBeGreaterThan(0);
}));
Before, the test would go to the await line and go back to the test for the expect because the await is saying to do this later. Now with the tick, we are saying if they are any promises created, resolve them before moving forward.
Also, I think you're missing a Promise.resolve on loadMenus.
loadMenus: () => Promise.resolve(
[
{
id: 'my-menu',
label: 'My Menu',
icon: 'far fa-envelope fa-2x',
url: 'url'
}
] as NavOption[])
I am thinking the Promise.resolve is required so it can be awaited.
edit
I don't think the done callback will help you.
You can try using await fixture.whenStable() to wait for the promise(s). Try this:
describe('ngOnInit', () => {
it('should add navigation options', async () => {
component.ngOnInit();
await fixture.whenStable();
expect(component.options.length).toBeGreaterThan(0);
});
});

How do I set a new State to test an observable?

I have tried all the day to make a simple test in jasmine, but i think i am doing something wrong. I have a piece of code that i wish to test, but i can't go inside. I was trying to follow nrgx 7 documentation, but i failed.
The unit test below should test my enderecoFeatureSubscription. The store.setState({ cep: null, endereco: RES }) is doing nothing with the store, so my subscription doens't do anything
let component: FormComponent;
let fixture: ComponentFixture<FormComponent>;
let store: MockStore<ICepState>
const initialState = {
cep: null, endereco: null
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [FormComponent],
imports: [StoreModule.forRoot({}),],
providers: [
provideMockStore({ initialState: CEPSTATE })
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
store = TestBed.get(Store);
});
it('should test enderecoFeatureSubscription ', () => {
store.setState({ cep: null, endereco: RES })
expect(component.endereco).toEqual(RES)
});
Component
private enderecoFeatureSubscription = this.store.pipe(select(enderecoFeatureSelector)).subscribe((endereco: IEndereco | any) => {
if (!endereco) {
return;
}
this.endereco = endereco
})
If you can help i thank you, because i hav wasted a lot of time with it.
In ngrx ver. > 8.0.0, there is a method store.refreshState refreshes the state if you use store.setState on respective overridden selectors. Unfortunately, refreshState method does not exist in ngrx 7. There is an alternative to that - you should override the desired selector using store.overrideSelector like this -
it('should test enderecoFeatureSubscription ', () => {
store.overrideSelector(enderecoFeatureSelector, <put you mocked value>
fixture.detectChanges(); //MAKE sure to remove fixture.detectChanges() from beforeEach
expect(component.endereco).toEqual(RES)
});
i did some changes to my test work fine.
1 - Removed 'const initialState' and imported from my app state file.
2 - The type of MockStore, i changed to my app state type
3 - In the test, i set a new value to 'cepState.endereco' and call setState with initialState
4 - I changed 'store' for 'mockStore', but it doesn't make diference
5 - finally, i brought the right import
Look the code bellow:
describe('FormComponent', () => {
let component: FormComponent;
let fixture: ComponentFixture<FormComponent>;
let mockStore: MockStore<AppState>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [FormComponent],
imports: [
StoreModule.forRoot({ 'cepState': CepReducer })
],
providers: [provideMockStore({ initialState })]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
mockStore = TestBed.get(Store);
});
it('should test new endereco state', () => {
initialState.cepState.endereco = RES
mockStore.setState(initialState)
expect(component.endereco).toEqual(RES)
});
});

Mock specific graphql request in cypress when running e2e tests

When running e2e tests with Cypress, my goal is to mock a specific graphql query.
Currently, I can mock all requests like this:
cy.server();
cy.route('POST', '/graphql', {
data: {
foo: 'bar'
},
});
The problem is that this mocks all /graphql queries. It would be awesome if I somehow could say:
cy.route('POST', '/graphql', 'fooQuery', {
data: {
foo: 'bar'
},
});
In our application, we are using Apollo Graphql - and thus all queries are named.
With cypress 6.0 route and route2 are deprecated, suggesting the use of intercept. As written in the docs (https://docs.cypress.io/api/commands/intercept.html#Aliasing-individual-GraphQL-requests) you can mock the GraphQL requests in this way:
cy.intercept('POST', '/api', (req) => {
if (req.body.operationName === 'operationName') {
req.reply({ fixture: 'mockData.json'});
}
}
One way to go about it is to provide the mocked data for the graphql operations in question inside one fixture file
cypress/support/commands.js
Cypress.Commands.add('stubGraphQL', (graphQlFixture) => {
cy.fixture(graphQlFixture).then((mockedData) => {
cy.on('window:before:load', (win) => {
function fetch(path, { body }) {
const { operationName } = JSON.parse(body)
return responseStub(mockedData[operationName])
}
cy.stub(win, 'fetch', fetch).withArgs("/graphql").as('graphql');
});
})
})
const responseStub = result => Promise.resolve({
json: () => Promise.resolve(result),
text: () => Promise.resolve(JSON.stringify(result)),
ok: true,
})
//TODO how to get it to stop listening and trying to stub once the list of operations provided in fixture have been stubbed?
example fixture file cypress/fixtures/signInOperation.json (note that there are 2 operations in there and that's how you can specify which response to mock)
{
"SIGNIN_MUTATION": {
"data":{"signin":{"id":"ck896k87jac8w09343gs9bl5h","email":"sams#automation.com","name":"Sam","__typename":"User"}}
},
"CURRENT_USER_QUERY" : {
"data":{"me":{"id":"ck896k87jac8w09343gs9bl5h","email":"sams#automation.com","name":"!!Sam's Mock","permissions":["USER"],"cart":[{"id":"ck89gebgvse9w0981bhh4a147","quantity":5,"item":{"id":"ck896py6sacox0934lqc8c4bx","price":62022,"image":"https://res.cloudinary.com/deadrobot/image/upload/v1585253000/sickfitz/ecgqu4i1wgcj41pdlbty.jpg","title":"MensShoes","description":"Men's Shoes","__typename":"Item"},"__typename":"CartItem"},{"id":"ck89gec6mb3ei0934lmyxne52","quantity":5,"item":{"id":"ck896os7oacl90934xczopgfa","price":70052,"image":"https://res.cloudinary.com/deadrobot/image/upload/v1585252932/sickfitz/i7ac6fqhsebxpmnyd2ui.jpg","title":"WomensShoes2","description":"Women's Shoes","__typename":"Item"},"__typename":"CartItem"},{"id":"ck89gl45psely0981b2bvk6q5","quantity":7,"item":{"id":"ck89ghqkpb3ng0934l67rzjxk","price":100000,"image":"https://res.cloudinary.com/deadrobot/image/upload/v1585269417/sickfitz/eecjz883y7ucshlwvsbw.jpg","title":"watch","description":"Fancy Watch","__typename":"Item"},"__typename":"CartItem"}],"__typename":"User"}}
}
}
in your spec file
cy.stubGraphQL('signInOperation.json')
cy.visit(yourURL)
cy.get(loginButton).click()
With cypress 5.1, using the new route2 command it is very simple to mock GraphQL requests, for example:
cy.route2('/graphql', (req) => {
if(req.body.includes('operationName')){
req.reply({ fixture: 'mockData.json'});
}
});
I just added an if condition to evaluate if the body of the GraphQL request contains certain string as part of the query.
If that is true, then I reply back with a custom body loaded from a fixture.
Documentation for cy.route2():
https://docs.cypress.io/api/commands/route2.html
You can try this if want to use fixture for graphql response:
cy.intercept('POST', '/test_api/graphql', (req) => {
req.continue((res) => {
if (req.body.operationName === 'op_name') {
res.send({ fixture: 'MyFixture/xyz.json' }),
req.alias = 'graphql'
}
})
})

Why won't VueJS invoke methods from the created() function?

Learning VueJS and trying to do a simple API call on component load to put a list of repos onto my page. When I call and set the this.repos from the created() method, no problem. But if I set it as a method and then call it from this.getRepos nothing happens. No error, nothing. What am I missing about VueJS?
This works:
data: () => ({
msg: 'Github Repos',
ok: 'Im practically giving away these repos',
repos: [],
}),
methods: {
},
async created() {
const repos = await axios.get('https://api.github.com/orgs/octokit/repos');
this.repos = repos.data.map(repo =>
`<div class="box"><a href="${repo.html_url}">
${repo.name}
</div>`,
);
},
This DOES NOT work:
data: () => ({
msg: 'Github Repos',
ok: 'Im practically giving away these repos',
repos: [],
}),
methods: {
getRepos: async () => {
const repos = await axios.get('https://api.github.com/orgs/octokit/repos');
this.repos = repos.data.map(repo =>
`<div class="box"><a href="${repo.html_url}">
${repo.name}
</div>`,
);
},
},
created() {
this.getRepos();
},
Any ideas? Thanks!
It's simply because you used arrow functions here so that this.repos's this is bound to window object. Changing async () => {} to async function() {} will help you overcome it.
See demo
Note that you should not use an arrow function to define a method (e.g. plus: () => this.a++). The reason is arrow functions bind the parent context, so this will not be the Vue instance as you expect and this.a will be undefined.
reference
Another way to do an Axios call with Vue using then() method:
demo
created() {
axios.get('https://api.github.com/orgs/octokit/repos', {
params: {
type: 'all',
},
})
.then((res) => {
console.log('Success Response', res.data);
res.data.forEach((repo) => {
this.repos.push({ name: repo.name, url: repo.html_url, language: repo.language });
});
})
.catch((err) => {
console.log('Error', err);
});
},

Resources