Test that a value is correctly set before calling an asynchronous service in a component - testing

I'm writing unit tests for an Angular2 app in which a component is calling an asynchronous service when it is initialized in order to load its data. Before loading data, it should set a loading flag to true in order to show a spinner, and then the loading flag is set back to falseonce the data has been retrieved.
ngOnInit() {
this.reloadEmployees(this.filter);
}
reloadEmployees(filter: string) {
this.loading = true;
this.employeeService.getEmployees(filter).subscribe((results: Employee[]) => {
this.employees = results;
this.loading = false;
});
}
Here is how I wrote my test:
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [EmployeeComponent],
imports: [FormsModule, SpinnerModule, ModalModule, TranslateModule.forRoot()],
providers: [
{ provide: EmployeeService, useValue: employeeServiceStub },
]
});
fixture = TestBed.createComponent(EmployeeComponent);
component = fixture.componentInstance;
let employeeService = fixture.debugElement.injector.get(EmployeeService);
spy = spyOn(employeeService, 'getEmployees').and.returnValue(Observable.of(testEmployees));
});
it('should start loading employees when the component is initialized', fakeAsync(() => {
fixture.detectChanges();
expect(component.loading).toEqual(true);
}));
I was expecting the callback from the service to be run only if I call tick() in my test but apparently it is called anyway because component.loadingis already back to false when I check its value. Note that if I comment out the line that sets loading back to false in the callback of the component, the test passes.
Any idea how I should test that?

Rx.Observable.of seems to be synchronous (GitHub issue). That's why fakeAsync wrapper doesn't work with it.
Instead you can use e.g. Observable.fromPromise(Promise.resolve(...)).
In your case:
spy = spyOn(employeeService, 'getEmployees').and.returnValue(
Observable.fromPromise(Promise.resolve(testEmployees)));
Alternatively you can use async scheduler:
import { Scheduler } from 'rxjs/Rx';
...
spy = spyOn(employeeService, 'getEmployees').and.returnValue(
Observable.of(testEmployees, Scheduler.async));
I have prepared a working test sample on Plunkr

Related

Vitest LitElement events

I have a storybook project where i am using Vite and LitElement components.
To test the components i thought i would use the Vitest library.
But i can't really test my components, it is like if the components aren't initialized / mounted / working (but they work fine in the stories., so i think the problem is with the testing).
I have a breadcrumb component, which dispatches a custom event on the connectedCallback function. On my story i can listen to this event, so i know it is being dispatched.
But i can seem to test it.
What i have:
on the breadcrumb component, inside the connectedCallback function
this.dispatchEvent(new Event('abc-breadcrumb-connected'));
on my breadcrumb.test.ts file:
import type { IWindow } from 'happy-dom';
import { expect, describe, it, beforeEach, vi } from 'vitest';
import '../abc-breadcrumb';
import { AbcBreadcrumb } from "../abc-breadcrumb";
declare global {
interface Window extends IWindow {}
}
describe('Abc breadcrumb', async () => {
it('Dispatches connected event', async () => {
const mockConnectedCallback = vi.fn(() => true);
window.addEventListener('abc-breadcrumb-connected', () => {
console.log('GOT THE EVENT');
mockConnectedCallback()
});
document.body.innerHTML = `
<abc-breadcrumb role="nav" aria-label="Breadcrumb" class="breadcrumb" ismobile="">
...
</abc-breadcrumb>
`;
await window.happyDOM.whenAsyncComplete();
await new Promise(resolve => setTimeout(resolve, 0));
expect(mockConnectedCallback).toHaveBeenCalled();
})
});
0n my vite.config.ts i have:
export default defineConfig({
test: {
globals: true,
environment: 'happy-dom',
},
...
})
the error i get:
AssertionError: expected "spy" to be called at least once
I have no idea why it isn't working an would be really happy to get some help.
Thanks!
At first look, using await new Promise(resolve => setTimeout(resolve, 0)); seems like the kind of thing I was unsuccessfully relying on myself to ensure enough ticks had passed for various operations to complete. This often worked in my local browser but failed in CI or resulted in flaky tests.
Why not set up the promise such that the event listener calls resolve() or possibly even mockConnectedCallback(). Then you can be certain the event isn't firing as opposed to only not having been fired when setTimeout resolves.
const mockConnectedCallback = vi.fn(() => true);
let connectedResolve;
const connectedPromise = new Promise(r => connectedResolve = r);
window.addEventListener('abc-breadcrumb-connected', () => {
console.log('GOT THE EVENT');
connectedResolve();
mockConnectedCallback();
});
document.body.innerHTML = `
<abc-breadcrumb role="nav" aria-label="Breadcrumb" class="breadcrumb" ismobile="">
...
</abc-breadcrumb>
`;
await window.happyDOM.whenAsyncComplete();
await connectedPromise;
expect(mockConnectedCallback).toHaveBeenCalled();
It makes the expect() a little redundant given that it won't be reached until the awaited promise resolves, but I think awaiting an explicit resolution makes things easier to reason about and doesn't hard-code various assumptions about microtask queues and things into the test code, which more often than not have come back to bite me later.

Can somone help to write test-case for successful component destruction in StencilJs?

I am writing Unit test cases for SentcilJs Component.
it('render component-name component', async () => {
const page = await newSpecPage({
components: [componentName],
html: `<component-name model-id="${Model.id}"></component-name>`,
});
page.rootInstance.Data = {
Model: [Model],
Value: [Value],
Status: []
}
page.rootInstance.tableModel = tableModel;
page.rootInstance.tableValues = tableValues;
await page.waitForChanges();
const chart = page.root.shadowRoot.querySelector('component-html');
expect(chart.innerHTML).not.toBeNull();
});
this one is for successful render of components.
I would like to have test-case successful component destruction.
can someone help here?
I tried calling the the disconnectedCallback directly from the test and expected the mocked methods to have been called.
service.cleanup = jest.fn();
page.rootInstance.disconnectedCallback();
expect(service.cleanup).toHaveBeenCalled();

Change mockimplementation on specific functions within a manual mocked service

I have a service.
export const PostService = jest.fn().mockReturnValue({
findPost: jest.fn().mockResolvedValue(false),
updatePosts: jest.fn().mockResolvedValue(false),
});
I import the service into my (nestjs) test module and mock it.
import { PostService } from '../post.service';
jest.mock('../post.service')
const module: TestingModule = await Test.createTestingModule({
controllers: [PostController],
providers: [PostService]
}).compile();
postService = module.get<PostService>(PostService);
I want to change the implementation of functions inside the mocked postService for different tests.
test('findPost should return false', () => {
postController.getPost() // This calls postService.findPost(), which returns false
})
test('findPost should return true', () => {
// I'm trying to change the implementation here, and then calling the controller
postService.findPost.mockImplementation(() => true) // Error: mockImplementation is not a function
postController.getPost() // This should call postService.findPost() and return true
})
How can I change the implementation of any of the functions inside the mocked service depending on the test cases? For example, if I want to test a service method that throws an error depending on the parameters.
Been testing for two weeks, reading the jest docs, trying jest.doMock, messing around with the factory parameter, and importing the service per test and mocking it per test case. All the examples I could find of changing the mockImplementation per test case is for a single mocked function, not a jest function returning an object that contains multiple functions.
It turns out the solution is simple, all I needed was:
jest.spyOn("service, "method").mockImplementation(() => { implementation... })
This can change the implementation of any mock function in any test cases.
I am usually do it like this
const serviceMock = jest.fn(() => ({
methodMock(): () => { ... }
})
Then, in beforeEach function add this service as a provider
const module: TestingModule = await Test.createTestingModule({
controllers: [MyController],
providers: [
{
provide: MyService,
useValue: serviceMock,
},
],
}).compile();
controller = module.get<MyController>(MyController);
And if i want to do this only for some of the test cases, i just add this code to testcase. If i need to use it in a bunch of test cases, i wrap it in a function

How Test with Jest a function in the method "mounted" VueJS

I would to try call a function already mocked. I use vueJS for the frond and Jest as unit test. Below a example of my code. My purpose is to test the call of « anotherFunction". The first test is succeed , not the second.Thanks for help or suggestion
code vueJS:
mounted() {
this.myfunction();
}
methods: {
myfunction() {
this.anotherFunction();
}
}
Jest code:
describe('Home.vue', () => {
let wrapper = null;
const options = {
mocks: {
$t: () => 'some specific text',
},
methods: {
myFunction: jest.fn(),
},
};
it('Should renders Home Component', () => {
// Given
wrapper = shallowMount(Home, options);
// Then
expect(wrapper).toBeTruthy();
});
it('Should call anotherFunction', async (done) => {
// Given
wrapper.vm.anotherFunction = jest.fn().mockResolvedValue([]);
// When
await wrapper.vm.myFunction();
// THIS THE PROBLEM, myFunction is mocked and I can't call the function 'anotherFunction' inside...
// Then
// expect(wrapper.vm.anotherFunction).toHaveBeenCalled();
});
});
I was finding a good way to help you if this test case. So, I thought in something like the chuck code below:
import { mount } from '#vue/test-utils';
describe('Home', () => {
it('method calls test case', () => {
const anotherMethodMock = jest.fn();
wrapper = mount(Home, {
methods: {
anotherMethod: anotherMethodMock
}
});
expect(anotherMethodMock).toHaveBeenCalled();
});
});
But, the Jest threw the following exception:
[vue-test-utils]: overwriting methods via the methods property is deprecated and will be removed in the next major version. There is no clear migration path for themethods property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex m ethod extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I had the following insight, maybe, in this case, should be better to test the side effect of this anotherMethod calling. What does it change? Is something being shown to the user?
I believe that here we have started from the wrong concept.
I hope that this tip could be useful :)
As suggested by #Vinícius Alonso, We should avoid using methods and setMethods in our test cases because of it's deprecation. But you can still test the mounted lifecycle by mocking the functions that are being called during mount. So you can do something similar to below snippet.
describe('Mounted Lifecycle', () => {
const mockMethodOne = jest.spyOn(MyComponent.methods, 'methodOne');
const mockMethodTwo = jest.spyOn(MyComponent.methods, 'methodTwo');
it('Validate data and function call during mount', () => {
const wrapper = shallowMount(MyComponent);
expect(mockMethodOne).toHaveBeenCalled();
expect(mockMethodTwo).toHaveBeenCalled();
})
})
Do mount/shallowMount inside it only rather putting it outside of it as it was not working in my case. You can checkout more details on it if you want.

Nuxt Ava End-to-End Testing Store Configuration

Given the example official Nuxt end-to-end test example using Ava:
import test from 'ava'
import { Nuxt, Builder } from 'nuxt'
import { resolve } from 'path'
// We keep a reference to Nuxt so we can close
// the server at the end of the test
let nuxt = null
// Init Nuxt.js and start listening on localhost:4000
test.before('Init Nuxt.js', async t => {
const rootDir = resolve(__dirname, '..')
let config = {}
try { config = require(resolve(rootDir, 'nuxt.config.js')) } catch (e) {}
config.rootDir = rootDir // project folder
config.dev = false // production build
config.mode = 'universal' // Isomorphic application
nuxt = new Nuxt(config)
await new Builder(nuxt).build()
nuxt.listen(4000, 'localhost')
})
// Example of testing only generated html
test('Route / exits and render HTML', async t => {
let context = {}
const { html } = await nuxt.renderRoute('/', context)
t.true(html.includes('<h1 class="red">Hello world!</h1>'))
})
// Close the Nuxt server
test.after('Closing server', t => {
nuxt.close()
})
How can you use Nuxt or Builder to configure/access the applications Vuex store? The example Vuex store would look like:
import Vuex from "vuex";
const createStore = () => {
return new Vuex.Store({
state: () => ({
todo: null
}),
mutations: {
receiveTodo(state, todo) {
state.todo = todo;
}
},
actions: {
async nuxtServerInit({ commit }, { app }) {
console.log(app);
const todo = await app.$axios.$get(
"https://jsonplaceholder.typicode.com/todos/1"
);
commit("receiveTodo", todo);
}
}
});
};
export default createStore;
Currently trying to run the provided Ava test, leads to an error attempting to access #nuxtjs/axios method $get:
TypeError {
message: 'Cannot read property \'$get\' of undefined',
}
I'd be able to mock $get and even $axios available on app in Vuex store method nuxtServerInit, I just need to understand how to access app in the test configuration.
Thank you for any help you can provide.
Just encountered this and after digging so many tutorial, I pieced together a solution.
You have essentially import your vuex store into Nuxt when using it programmatically. This is done by:
Importing Nuxt's config file
Adding to the config to turn off everything else but enable store
Load the Nuxt instance and continue your tests
Here's a working code (assuming your ava and dependencies are set up)
// For more info on why this works, check this aweomse guide by this post in getting this working
// https://medium.com/#brandonaaskov/how-to-test-nuxt-stores-with-jest-9a5d55d54b28
import test from 'ava'
import jsdom from 'jsdom'
import { Nuxt, Builder } from 'nuxt'
import nuxtConfig from '../nuxt.config' // your nuxt.config
// these boolean switches turn off the build for all but the store
const resetConfig = {
loading: false,
loadingIndicator: false,
fetch: {
client: false,
server: false
},
features: {
store: true,
layouts: false,
meta: false,
middleware: false,
transitions: false,
deprecations: false,
validate: false,
asyncData: false,
fetch: false,
clientOnline: false,
clientPrefetch: false,
clientUseUrl: false,
componentAliases: false,
componentClientOnly: false
},
build: {
indicator: false,
terser: false
}
}
// We keep a reference to Nuxt so we can close
// the server at the end of the test
let nuxt = null
// Init Nuxt.js and start listening on localhost:5000 BEFORE running your tests. We are combining our config file with our resetConfig using Object.assign into an empty object {}
test.before('Init Nuxt.js', async (t) => {
t.timeout(600000)
const config = Object.assign({}, nuxtConfig, resetConfig, {
srcDir: nuxtConfig.srcDir, // don't worry if its not in your nuxt.config file. it has a default
ignore: ['**/components/**/*', '**/layouts/**/*', '**/pages/**/*']
})
nuxt = new Nuxt(config)
await new Builder(nuxt).build()
nuxt.listen(5000, 'localhost')
})
// Then run our tests using the nuxt we defined initially
test.serial('Route / exists and renders correct HTML', async (t) => {
t.timeout(600000) // Sometimes nuxt's response is slow. We increase the timeont to give it time to render
const context = {}
const { html } = await nuxt.renderRoute('/', context)
t.true(html.includes('preload'))
// t.true(true)
})
test.serial('Route / exits and renders title', async (t) => {
t.timeout(600000)
const { html } = await nuxt.renderRoute('/', {})
const { JSDOM } = jsdom // this was the only way i could get JSDOM to work. normal import threw a functione error
const { document } = (new JSDOM(html)).window
t.true(document.title !== null && document.title !== undefined) // simple test to check if site has a title
})
Doing this should work. HOWEVER, You may still get some errors
✖ Timed out while running tests. If you get this you're mostly out of luck. I thought the problem was with Ava given that it didn't give a descriptive error (and removing any Nuxt method seemed to fix it), but so far even with the above snippet sometimes it works and sometimes it doesn't.
My best guess at this time is that there is a delay on Nuxt's side using either renderRouter or renderAndGetWindow that ava doesn't wait for, but on trying any of these methods ava almost immediately "times out" despite the t.timeout being explicitly set for each test. So far my research has lead me to checking the timeout for renderAndGetWindow (if it exists, but the docs doesn't indicate such).
That's all i've got.