I am trying to implement some unit tests on a form to see if the validation rules are working as expected.
from this page : https://github.com/aurelia/testing/issues/63
I found this implementation : https://github.com/aurelia/validation/blob/master/test/validate-binding-behavior.ts
and I tried to implement it in my project
login.spec.js
import {bootstrap} from 'aurelia-bootstrapper';
import {StageComponent} from 'aurelia-testing';
import {PLATFORM} from 'aurelia-pal';
import { configure, blur, change } from './shared';
import { Login } from './login';
describe('ValidateBindingBehavior', () => {
it('sets validateTrigger', (done) => {
const component = StageComponent
.withResources(PLATFORM.moduleName('features/account/login/login'))
.inView('<login></login>')
.boundTo({});
component.bootstrap(configure);
let viewModel;
const renderer = { render: jasmine.createSpy() };
component.create(bootstrap)
// grab some references.
.then(() => {
viewModel = component.viewModel;
viewModel.controller.addRenderer(renderer);
})
.then(() => expect(viewModel.controller.errors.length).toBe(0))
.then(() => blur(viewModel.firstName))
.then(() => expect(viewModel.controller.errors.length).toBe(1))
.then(() => component.dispose())
.then(done);
});
});
login.js
import { inject, NewInstance } from 'aurelia-dependency-injection';
import { ValidationController } from 'aurelia-validation';
import { User } from './login.model';
#inject(NewInstance.of(ValidationController), User)
export class Login {
constructor(controller, user) {
this.controller = controller;
this.firstName = '';
this.lastName = '';
this.userName = '';
this.showForm = true;
this.user = user;
}
};
login.model.js
import {ValidationRules} from 'aurelia-validation';
export class User {
firstName = '';
lastName = '';
userName = '';
constructor() {
ValidationRules
.ensure('firstName')
.required()
.ensure('lastName')
.required()
.minLength(10)
.ensure('userName')
.required()
.on(this);
}
}
shared.js
import {DOM, PLATFORM} from 'aurelia-pal';
export function configure(aurelia) {
return aurelia.use
.standardConfiguration()
.plugin(PLATFORM.moduleName('aurelia-validation'))
}
export function blur(element) {
element.dispatchEvent(DOM.createCustomEvent('blur', {}));
return new Promise(resolve => setTimeout(resolve));
}
export function change(element, value) {
element.value = value;
element.dispatchEvent(DOM.createCustomEvent('change', { bubbles: true }));
return new Promise(resolve => setTimeout(resolve));
}
and here is a piece of html markup :
<div>
<input ref="firstName" type="text" value.bind="user.firstName & validateOnBlur"
validation-errors.bind="firstNameErrors">
<label style="display: block;color:red" repeat.for="errorInfo of firstNameErrors">
${errorInfo.error.message}
</label>
</div>
<div>
in the spec, when I blur the element I expect to get one error, but "controller.errors" is always an empty array. and I get this for the failed message :
Error: Expected 0 to be 1.
UPDATE 1:
I tried to validate manually, so I added this in my spec :
.then(()=>
viewModel.controller.validate({object: viewModel.user, propertyName: 'firstName' })
)
and it works fine, but the blur and change functions don't trigger validation.
UPDATE 2:
I changed it like "Sayan Pal" suggested. and it works now but with a tiny problem. when I "blur" the element once it shows one error. but when I "blur" several elements ( let's say three ) it doesn't show the last error. in this case controller.errors.length would be 2.
I can blur the last element two times to get the correct length of errors. but I think there should be a better solution.
.then(() => blur(viewModel.firstName))
.then(() => blur(viewModel.userName))
.then(() => blur(viewModel.lastName))
.then(() => blur(viewModel.lastName))
I think instead of using createCustomEvent you simply need to do element.dispatchEvent(new Event("blur"));. Same goes for change event.
This has always worked for me, and hope it will help you too :)
On related note, I use a default ValidationController generator factory method that ensures the default trigger as follows.
import { validateTrigger, ValidationControllerFactory } from "aurelia-validation";
...
const validationController = validationControllerFactory.createForCurrentScope();
validationController.changeTrigger(validateTrigger.changeOrBlur);
Update after OP updated the question
It is difficult to say why it is happening, without debugging. As I don't see any imminent problem in your test code, my assumption is that it is a timing issue. The main idea is that you need to wait for the change to happen. There are several ways you can do it, all of those needs change in how you are asserting.
One way to do it is to employ a promise with a timeout that polls in a regular interval for the change. And then wait for the promise.
Or you can use TaskQueue to queue your assertion, and after the assertion call done. This looks something like below.
new TaskQueue().queueMicroTask(() => {
expect(foo).toBe(bar);
done();
});
Other alternative is to use cypress as an e2e test framework. Out of the box, Cypress waits for the change to happen until times out.
Choose what best fits your need.
Related
I'm trying to define a simple assertion package in TypeScript so that assertions can be turned off during efficiency testing (and production eventually). Here's my attempt for a simple module:
let assertionsEnabled = true;
export function withAssertions<T>(val : boolean, body : () => T) : T {
const savedEnabled = assertionsEnabled;
try {
assertionsEnabled = val;
return body();
} finally {
assertionsEnabled = savedEnabled;
}
}
export function assert(condition : () => boolean, message ?: string) {
if (assertionsEnabled) {
if (!condition()) {
throw new Error("assertion failed" + (message ?? ""));
}
}
}
The problem is that both the Jest test and the class being tested each import this module and they seem to end up with two different assertionsEnabled variables. My test is encoded with
describe('building', () => {
withAssertions(false, () => {
...
});
});
and in the ADT, I have a bunch of assertions, but even though assertions are turned off in the Jest test, they are still on in the ADT. I thought modules were supposed to have a single instance. But obviously I'm doing something wrong.
I'm betting you have asynchronous stuff going on in your tests or stuff you instantiate as part of exporting from the module and it's happening before the jest stuff starts being executed.
Also, you might instead want to do a describeWithAssertion function instead, like so...
function describeWithAssertion(
ae: boolean,
title: string,
f: any // this should be specified better
) {
const prevAE = assertionsEnabled;
return describe(
title,
() => {
beforeEach(() => assertionsEnabled = ae);
afterEach(() => assertionsEnabled = prevAE);
f();
}
);
}
This way, even if f contains async functionality, jest is worrying about it, not you.
I've tried to search for answer to this problem for some time now and I failed. So I decided to give it a try here. If there is a such question already and I missed it, I'm sorry for duplicate.
Assume I have this javascript module 'myValidator.js', where are two functions and one function calls the other one.
export const validate = (value) => {
if (!value.something) {
return false
}
// other tests like that
return true
}
export const processValue = (value) => {
if (!validate(value)) {
return null
}
// do some stuff with value and return something
}
Test for this like this.
I want to test the validate function, whether is behaves correctly. And then I have the processValue function that invokes the first one and returns some value when validation is ok or null.
import * as myValidator from './myValidator'
describe('myValidator', () => {
describe('validate', () => {
it('should return false when something not defined', () => {
...
}
}
describe('processValue', () => {
it('should return something when value is valid', () => {
const validateMock = jest.spyOn(myValidator, 'validate')
validateMock.mockImplementation(() => true)
expect(validate('something')).toEqual('somethingProcessed')
}
it('should return null when validation fails', () => {
const validateMock = jest.spyOn(myValidator, 'validate')
validateMock.mockImplementation(() => false)
expect(validate('somethingElse')).toEqual(null)
}
}
}
Now, the problem is that this doesn't actually work as processValue() actually calls the function inside the module, because of the closure I suppose. So the function is not mocked as only the reference in exports is changed to jest mock, I guess.
I have found a solution for this and inside the module to use
if (!exports.validate(value))
That works for the tests. However we use Webpack (v4) to build the app, so it transforms those exports into its own structure and then when the application is started, the exports is not defined and the code fails.
What's best solution to test this?
Sure I can do it with providing some valid and invalid value, for this simple case that would work, however I believe it should be tested separately.
Or is it better to not mock functions and call it through to avoid the problem I have or is there some way how to achieve this with JavaScript modules?
I finally found the answer to this question. It's actually in the the Jest examples project on GitHub.
// Copyright 2004-present Facebook. All Rights Reserved.
/**
* This file illustrates how to do a partial mock where a subset
* of a module's exports have been mocked and the rest
* keep their actual implementation.
*/
import defaultExport, {apple, strawberry} from '../fruit';
jest.mock('../fruit', () => {
const originalModule = jest.requireActual('../fruit');
const mockedModule = jest.genMockFromModule('../fruit');
//Mock the default export and named export 'apple'.
return {
...mockedModule,
...originalModule,
apple: 'mocked apple',
default: jest.fn(() => 'mocked fruit'),
};
});
it('does a partial mock', () => {
const defaultExportResult = defaultExport();
expect(defaultExportResult).toBe('mocked fruit');
expect(defaultExport).toHaveBeenCalled();
expect(apple).toBe('mocked apple');
expect(strawberry()).toBe('strawberry');
});
We are porting a very large project into Vue.js and would like to thoroughly implement our unit testing suite as we go.
We would like to validate that components are having all their required properties set at creation, my test case looks something like:
describe('Input', () => {
it('fail initialization with no value', () => {
const vm = mount(Input, {
propsData: {},
});
// expecting above to fail due to required prop 'value'
});
});
The component Input is expected to contain a property value. I can see via the console warning emitted that it is missing but we would like to catch this in our unit tests. Directly testing this doesn't make much sense but once we have components nesting several components, making sure that each component correctly initialises its sub components is important and this is achieved by assuring ourselves that the above test should fail and be caught failing.
There is no exception raised however, we have had ideas to hook into the Vue.warnHandler but this just seems like a lot of work for simply confirming that a constructor works as expected.
Is there a recommended approach to doing this with Vue.js?
Unfortunately it looks like the only way to do this is to hook into the Vue.warnHandler
I used a variable and reset it on the afterEach hook (Mocha) like so:
let localVue;
let hasWarning = false;
function functionToWrapShallowMount(propsData) {
const Wrapper = shallowMount(Control, {
propsData,
provide: {
$validator: () => {}
},
localVue
});
return Wrapper;
}
before(() => {
localVue = createLocalVue();
Vue.config.warnHandler = () => {
hasWarning = true;
};
});
afterEach(() => {
hasWarning = false;
});
it('throws a warning', () => {
const { vm } = functionToWrapShallowMount({
name: 'foooo',
value: 'bad'
});
vm.name.should.eq('foooo');
hasWarning.should.eq(true);
});
The catch to this is you cannot use the localVue from Vue test utils you must use the Vue instance imported from vue.
I am testing an angular app and especially this HTML input:
<form name="editForm" role="form" novalidate (ngSubmit)="save()" #editForm="ngForm">
<input type="text" name="nombre" id="field_nombre"
[(ngModel)]="paciente.nombre" required/>
(etc. f.e. button on submit...)
Here is my component:
imports....
export class PacienteDialogComponent implements OnInit {
paciente: Paciente;
....
save() {
this.isSaving = true;
if (this.paciente.id !== undefined) {
this.subscribeToSaveResponse(
this.pacienteService.update(this.paciente));
} else {
this.subscribeToSaveResponse(
this.pacienteService.create(this.paciente));
}
}
}
Here is my patient.model.ts
export class Paciente implements BaseEntity {
constructor(
public id?: number,
public nombre?: string,
public sexo?: Sexo,
.....
I want to test the form which means that on submit it is really calling teh save() function.
I have this in my test:
describe('Paciente Management Dialog Component', () => {
let comp: PacienteDialogComponent;
let fixture: ComponentFixture<PacienteDialogComponent>;
let debugElement: DebugElement; //create a debgElement for testing
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [OncosupTestModule,
OncosupSharedModule,
BrowserModule,
FormsModule,
],
declarations:...
],
providers: [
...
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PacienteDialogComponent);
comp = fixture.componentInstance;
debugElement = fixture.debugElement;
});
//a default generated test which controls if the save method really saves a new patient with its name, id, sex, etc.
it('Should call create service on save for new entity',
inject([],
fakeAsync(() => {
// GIVEN
const entity = new Paciente();
spyOn(service, 'create').and.returnValue(Observable.of(new HttpResponse({body: entity})));
comp.paciente = entity;
// WHEN
comp.save();
tick(); // simulate async
// THEN
expect(service.create).toHaveBeenCalledWith(entity);
expect(comp.isSaving).toEqual(false);
expect(mockEventManager.broadcastSpy).toHaveBeenCalledWith({ name: 'pacienteListModification', content: 'OK'});
expect(mockActiveModal.dismissSpy).toHaveBeenCalled();
})
)
);
// And teh second thing I want to test is if ngSubmit is really calling the save() function
it ('should call the onSubmit method', async(() => {
//fixture.detectChanges();
spyOn(comp,'save');
var1 = debugElement.query(By.css('button')).nativeElement;
console.log('print button ' + var1);
var1.click();
expect(comp.save).toHaveBeenCalledTimes(0);//verify...
}));
//And also if isSaving is set to true
it ('should set isSaving to true', async(() => {
comp.save();
expect(comp.isSaving).toBeTruthy();
}));
1.Now I have these questions: The first test is generated by default and not written by me. In this line const entity = new Paciente(); should I call parameters of Paciente? Like id, sex, name or leave it like this by default without parameters. Th epurpose of this first test if to check if really the save() function saves a patient and his data like id, sex, etc.
2.For the second test I read it in a tutorial of angular that: HaveBennCalled(0) is the right thing to test if this spy is called and how many times. But anyway does it really tests if the button calls the function save(). I think it only checks if thebutton havenĀ“t been called before, but not if it is callled right now in save function.
3.And are these 3 tests enough and complete for a form submitting?
Following my comments, here is how to test if a form is submitted correctly.
Let's say you have an interface Patient :
export interface Patient {
id: number;
name: string;
}
In your component, you have a form, and you submit it through submit() :
submit() {
this.patientService.savePatient(this.patient).subscribe(result => {
console.log('Patient created');
});
}
Now your service make the HTTP call and checks if the fields are okay :
savePatient(patient: Patient): Observable<any> {
if (typeof patient.id !== number) { return Observable.throw('ID is not a number'); }
if (typeof patient.name !== string) { return Observable.throw('Name is not a string'); }
return this.http.post<any>(this.url, patient);
}
Then your tests should look like this. First, the component :
it('Should call the service to save the patient in DB', () => {
// Spy on service call
// Expect spy to have been called
});
it('Should log a message on success', () => {
// Spy on console log
// Expect console log to have been called with a string
});
You can also test if the error is treated correctly, if you have error codes, etc.
Now in the service :
it('Should throw an error if the ID is not a number', () => {
// Mock a patient with a string ID
// Expect an error to be thrown
});
// Same thing for the name, you get the idea
it('Should make an HTTP call with a valid patient', () => {
// Spy on the HttpTestingController
// Expect the correct endpoint to have been called, with the patient as the payload
});
The general idea of those tests is to cover any case that could happen. This will allow you to prevent side effects : for instance, if one day you decide to pass your ID to string, the unit test will fail and tell you
You expect me to send a string but I pass only with a number
This is the purpose of a unit test.
I create a project using create-app-component, which configures a new app with build scripts (babel, webpack, jest).
I wrote a React component that I'm trying to test. The component is requiring another javascript file, exposing a function.
My search.js file
export {
search,
}
function search(){
// does things
return Promise.resolve('foo')
}
My react component:
import React from 'react'
import { search } from './search.js'
import SearchResults from './SearchResults'
export default SearchContainer {
constructor(){
this.state = {
query: "hello world"
}
}
componentDidMount(){
search(this.state.query)
.then(result => { this.setState({ result, })})
}
render() {
return <SearchResults
result={this.state.result}
/>
}
}
In my unit tests, I want to check that the method search was called with the correct arguments.
My tests look something like that:
import React from 'react';
import { shallow } from 'enzyme';
import should from 'should/as-function';
import SearchResults from './SearchResults';
let mockPromise;
jest.mock('./search.js', () => {
return { search: jest.fn(() => mockPromise)};
});
import SearchContainer from './SearchContainer';
describe('<SearchContainer />', () => {
it('should call the search module', () => {
const result = { foo: 'bar' }
mockPromise = Promise.resolve(result);
const wrapper = shallow(<SearchContainer />);
wrapper.instance().componentDidMount();
mockPromise.then(() => {
const searchResults = wrapper.find(SearchResults).first();
should(searchResults.prop('result')).equal(result);
})
})
});
I already had a hard time to figure out how to make jest.mock work, because it requires variables to be prefixed by mock.
But if I want to test arguments to the method search, I need to make the mocked function available in my tests.
If I transform the mocking part, to use a variable:
const mockSearch = jest.fn(() => mockPromise)
jest.mock('./search.js', () => {
return { search: mockSearch};
});
I get this error:
TypeError: (0 , _search.search) is not a function
Whatever I try to have access to the jest.fn and test the arguments, I cannot make it work.
What am I doing wrong?
The problem
The reason you're getting that error has to do with how various operations are hoisted.
Even though in your original code you only import SearchContainer after assigning a value to mockSearch and calling jest's mock, the specs point out that: Before instantiating a module, all of the modules it requested must be available.
Therefore, at the time SearchContainer is imported, and in turn imports search , your mockSearch variable is still undefined.
One might find this strange, as it would also seem to imply search.js isn't mocked yet, and so mocking wouldn't work at all. Fortunately, (babel-)jest makes sure to hoist calls to mock and similar functions even higher than the imports, so that mocking will work.
Nevertheless, the assignment of mockSearch, which is referenced by the mock's function, will not be hoisted with the mock call. So, the order of relevant operations will be something like:
Set a mock factory for ./search.js
Import all dependencies, which will call the mock factory for a function to give the component
Assign a value to mockSearch
When step 2 happens, the search function passed to the component will be undefined, and the assignment at step 3 is too late to change that.
Solution
If you create the mock function as part of the mock call (such that it'll be hoisted too), it'll have a valid value when it's imported by the component module, as your early example shows.
As you pointed out, the problem begins when you want to make the mocked function available in your tests. There is one obvious solution to this: separately import the module you've already mocked.
Since you now know jest mocking actually happens before imports, a trivial approach would be:
import { search } from './search.js'; // This will actually be the mock
jest.mock('./search.js', () => {
return { search: jest.fn(() => mockPromise) };
});
[...]
beforeEach(() => {
search.mockClear();
});
it('should call the search module', () => {
[...]
expect(search.mock.calls.length).toBe(1);
expect(search.mock.calls[0]).toEqual(expectedArgs);
});
In fact, you might want to replace:
import { search } from './search.js';
With:
const { search } = require.requireMock('./search.js');
This shouldn't make any functional difference, but might make what you're doing a bit more explicit (and should help anyone using a type-checking system such as Flow, so it doesn't think you're trying to call mock functions on the original search).
Additional note
All of this is only strictly necessary if what you need to mock is the default export of a module itself. Otherwise (as #publicJorn points out), you can simply re-assign the specific relevant member in the tests, like so:
import * as search from './search.js';
beforeEach(() => {
search.search = jest.fn(() => mockPromise);
});
In my case, I got this error because I failed to implement the mock correctly.
My failing code:
jest.mock('react-native-some-module', mockedModule);
When it should have been an arrow function...
jest.mock('react-native-some-module', () => mockedModule);
When mocking an api call with a response remember to async() => your test and await the wrapper update. My page did the typical componentDidMount => make API call => positive response set some state...however the state in the wrapper did not get updated...async and await fix that...this example is for brevity...
...otherImports etc...
const SomeApi = require.requireMock('../../api/someApi.js');
jest.mock('../../api/someApi.js', () => {
return {
GetSomeData: jest.fn()
};
});
beforeEach(() => {
// Clear any calls to the mocks.
SomeApi.GetSomeData.mockClear();
});
it("renders valid token", async () => {
const responseValue = {data:{
tokenIsValid:true}};
SomeApi.GetSomeData.mockResolvedValue(responseValue);
let wrapper = shallow(<MyPage {...props} />);
expect(wrapper).not.toBeNull();
await wrapper.update();
const state = wrapper.instance().state;
expect(state.tokenIsValid).toBe(true);
});