Since couple evening I've played with form validation in augular2.
All basic cases were easy to implement and they works fine but I stick with asynchronous validation. I have created a very tiny example http://plnkr.co/edit/Xo8xwJjhlHkXrunzS8ZE and it didn't work.
According to test "should fire an event after the status has been updated to pending" from model_spec.ts Registration via creation of control group suppose to work in a way
builder.group({login: ["",Validators.required,validationFuctionWhichReturnsPromise]
I spent a full evening to discovered that this code has been released in alfa-46 (and I used alfa-45) and after update depencies the async validation started to work. The feature is very fresh and is not fully documented but
(for those who haven't tried it yet) Basically async validator is a function which have a Control argument and return a promise which validation result. There are two ways to register a validator. 1) the one which I used in my example and 2) as a directive which Provide validators via NG_ASYNC_VALIDATORS (See UniqLoginValidator and NgFormControl to see how it work). You can compose more than one validator (not tested yet but functions to do this are in code, see https://github.com/angular/angular/commit/cf449dd).
But when I finally reach to up and running validators a new problem arrived. Async validator is perfect to used it in server side validation. But the validation is invoked after each change of model.fe after each keyup. So if we will send request to a server after each key up, it won't be too efficient way ;) I checked how it is done in angular 1 and they is a possibility to debounce validation events.
My questions are:
How to implement throttle or debounce with async validators? I saw some ideas but none of them were fine (mostly because they need to change angular code itself). Is there any valid way to do this without waiting for new angular release ?
I was thinking about to warping a validator function with debounce (from underscorejs) but it will not work because angular expects to get a valid promise every time.
My second though was that if all event use RxJs under the hood then maybe I can apply debounce on stream of event which is responsible for validation. In model.ts the promise returned from validator is change to observable and a new subscribed is added. We don't have any access to obs(Observable) to apply debounce there.
Is there any way or id to change,easy extend a control over the form validation ?
I spotted a close related problem in How to trigger Form Validators in angular2
PS there is other issue related to async validators and it is still open https://github.com/angular/angular/issues/1068
Here is a helper class that you can use to debounce all your async validators:
import {Component} from 'angular2/core';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import {Control} from 'angular2/common';
export class AsyncValidator {
_validate;
constructor(validator: (control: Control) => any, debounceTime = 1000) {
let source: any = new Observable((observer: Observer<Control>) => {
this._validate = (control) => observer.next(control);
});
source.debounceTime(debounceTime)
.distinctUntilChanged(null, (x) => x.control.value)
.map(x => { return { promise: validator(x.control), resolver: x.promiseResolver }; })
.subscribe(
(x) => x.promise.then(resultValue => x.resolver(resultValue),
(e) => { console.log('async validator error: %s', e); }));
}
private _getValidator() {
return (control) => {
let promiseResolver;
let p = new Promise((resolve) => {
promiseResolver = resolve;
});
this._validate({ control: control, promiseResolver: promiseResolver });
return p;
};
}
static debounce(validator: (control: Control) => any, debounceTime = 400) {
var asyncValidator = new this(validator, debounceTime);
return asyncValidator._getValidator();
}
}
Then all you have to do where use async validators is just wrap your validator with this call and write your validator the same as you would normally:
AsyncValidator.debounce(control => this.asyncValidator(control));
Here is an example usage:
export class AppComponent {
form: ControlGroup;
constructor(private _formBuilder: FormBuilder) {
var validator = AsyncValidator.debounce(control => this.asyncValidator(control));
this.form = _formBuilder.group({
name: ['', Validators.required, validator],
});
}
asyncValidator(control): any {
let p = new Promise(resolve => {
// get from server information need to validate control
if (control.value === 'valid value') {
resolve(null);
} else {
resolve({
asyncValidator: {
valid: false
}
});
}
});
return p;
}
}
There is an awesome issue on angular site that deals with the problem of both debouncing and switchMapping the validation:
https://github.com/angular/angular/issues/6895
This is mine working solution (but all the credit goes to guys from thread)
class AsyncValidator{
private validatorInput: Subject<string>;
private validatorChain: Observable<any>;
constructor(service: ManageUsersService) {
this.validatorInput = new Subject();
this.validatorChain = this.validatorInput
.debounceTime(400)
.distinctUntilChanged()
.switchMap(value => service.findUsersByName(value)
.map(() => ({error: 'Error'})) //example of failed validation
.catch(() => Observable.of(null))) //example of successful validation
.do(v => console.log('mapped', v))
.share()
.take(1);
}
validate = (control: AbstractControl) => {
// An immediate timeout is set because the next has to occur after the
// validator chain is subscribed to.
setTimeout(() => this.validatorInput.next(control.value), 0);
return this.validatorChain;
}
You use it like this:
this.createUserForm = fb.group({
login: [ null,
Validators.required,
new AsyncValidator(userService).validate
],
});
}
Related
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.
I am busy with a little proof of concept where basically the requirement is to have the home page be a login screen when a user has not logged in yet, after which a component with the relevant content is shown instead when the state changes upon successful authentication.
I have to state upfront that I am very new to react and redux and am busy working through a tutorial to get my skills up. However, this tutorial is a bit basic in the sense that it doesn't deal with connecting with a server to get stuff done on it.
My first problem was to get props to be available in the context of the last then of a fetch as I was getting an error that this.props.dispatch was undefined. I used the old javascript trick around that and if I put a console.log in the final then, I can see it is no longer undefined and actually a function as expected.
The problem for me now is that nothing happens when dispatch is called. However, if I manually refresh the page it will display the AuthenticatedPartialPage component as expected because the localstorage got populated.
My understanding is that on dispatch being called, the conditional statement will be reavaluated and AuthenticatedPartialPage should display.
It feels like something is missing, that the dispatch isn't communicating the change back to the parent component and thus nothing happens. Is this correct, and if so, how would I go about wiring up that piece of code?
The HomePage HOC:
import React from 'react';
import { createStore, combineReducers } from 'redux';
import { connect } from 'react-redux';
import AuthenticatedPartialPage from './partials/home-page/authenticated';
import AnonymousPartialPage from './partials/home-page/anonymous';
import { loggedIntoApi, logOutOfApi } from '../actions/authentication';
import authReducer from '../reducers/authentication'
// unconnected stateless react component
const HomePage = (props) => (
<div>
{ !props.auth
? <AnonymousPartialPage />
: <AuthenticatedPartialPage /> }
</div>
);
const mapStateToProps = (state) => {
const store = createStore(
combineReducers({
auth: authReducer
})
);
// When the user logs in, in the Anonymous component, the local storage is set with the response
// of the API when the log in attempt was successful.
const storageAuth = JSON.parse(localStorage.getItem('auth'));
if(storageAuth !== null) {
// Clear auth state in case local storage has been cleaned and thus the user should not be logged in.
store.dispatch(logOutOfApi());
// Make sure the auth info in local storage is contained in the state.auth object.
store.dispatch(loggedIntoApi(...storageAuth))
}
return {
auth: state.auth && state.auth.jwt && storageAuth === null
? state.auth
: storageAuth
};
}
export default connect(mapStateToProps)(HomePage);
with the Anonymous LOC being:
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { loggedIntoApi } from '../../../actions/authentication';
export class AnonymousPartialPage extends React.Component {
constructor(props) {
super(props);
}
onSubmit = (e) => {
e.preventDefault();
const loginData = { ... };
// This is where I thought the problem initially occurred as I
// would get an error that `this.props` was undefined in the final
// then` of the `fetch`. After doing this, however, the error went
// away and I can see that `props.dispatch is no longer undefined
// when using it. Now though, nothing happens.
const props = this.props;
fetch('https://.../api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(loginData)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if(data && data.jwt) {
props.dispatch(loggedIntoApi(data));
localStorage.setItem('auth', JSON.stringify(data));
}
// else show an error on screen
});
};
render() {
return (
<div>
... onSubmit gets called successfully somewhere in here ...
</div>
);
}
}
export default connect()(AnonymousPartialPage);
the action:
// LOGGED_INTO_API
export const loggedIntoApi = (auth_token) => ({
type: 'LOGGED_INTO_API',
auth: auth_token
});
// LOGGED_OUT_OF_API
export const logOutOfApi = (j) => ({
type: 'LOG_OUT_OF_API'
});
and finally the reducer:
const authDefaultState = { };
export default (state = authDefaultState, action) => {
switch (action.type) {
case 'LOGGED_INTO_API':
// SOLUTION : changed this line "return action.auth;" to this:
return { ...action.auth, time_stamp: new Date().getTime() }
case 'LOG_OUT_OF_API':
return { auth: authDefaultState };
default:
return state;
}
};
My suggestion would be to make sure that the state that you are changing inside Redux is changing according to javascript's equality operator!. There is a really good answer to another question posted that captures this idea here. Basically, you can't mutate an old object and send it back to Redux and hope it will re-render because the equality check with old object will return TRUE and thus Redux thinks that nothing changed! I had to solve this issue by creating an entirely new object with the updated values and sending it through dispatch().
Essentially:
x = {
foo:bar
}
x.foo = "baz"
dispatch(thereWasAChange(x)) // doesn't update because the x_old === x returns TRUE!
Instead I created a new object:
x = {
foo:"bar"
}
y = JSON.parse(JSON.stringify(x)) // creates an entirely new object
dispatch(thereWasAChange(y)) // now it should update x correctly and trigger a rerender
// BE CAREFUL OF THE FOLLOWING!
y = x
dispatch(thereWasAChange(y)) // This WON'T work!!, both y and x reference the SAME OBJECT! and therefore will not trigger a rerender
Hope this helps!
I'm using the WebRTC library which has a very specific API. The peerConnection.setRemoteDescription method's 2nd argument is supposed to be a callback for when it finishes setting the remote description:
This is one of my wrapper functions for my WebRTC class:
export function setRemoteSdp(peerConnection, sdp, callback) {
if (!sdp) return;
return peerConnection.setRemoteDescription(
new RTCSessionDescription(sdp),
callback, // <-------------
);
}
And this is a sketch of what I want to do:
function receivedSdp(action$, store) {
return action$.ofType(VideoStream.RECEIVED_SDP)
.mergeMap(action => {
const {peerConnection} = store.getState().videoStreams;
const {sdp} = action.payload;
return WebRTC.setRemoteSdp(peerConnection, sdp, () => {
return myReducer.myAction(); // <------ return action as the callback
})
})
};
This doesn't work since I'm not returning an Observable. Is there a way to do this?
P.S. this is the WebRTC API: https://github.com/oney/react-native-webrtc/blob/master/RTCPeerConnection.js#L176
martin's answer is correct about using Observable.create or new Observable--same thing (except it's not clear to me why you need the mergeAll() since the mergeMap will flatten?)
As a bonus, you could also use Observable.bindCallback for this.
// bindCallback is a factory factory, it creates a function that
// when called with any arguments will return an Observable that
// wraps setRemoteSdp, handling the callback portion for you.
// I'm using setRemoteSdp.bind(WebRTC) because I don't know
// if setRemoteSdp requires its calling context to be WebRTC
// so it's "just in case". It might not be needed.
const setRemoteSdpObservable = Observable.bindCallback(WebRTC.setRemoteSdp.bind(WebRTC));
setRemoteSdpObservable(peerConnection, sdp)
.subscribe(d => console.log(d));
Usage inside your epic would be something like this
// observables are lazy, so defining this outside of our epic
// is totally cool--it only sets up the factory
const setRemoteSdpObservable = Observable.bindCallback(WebRTC.setRemoteSdp.bind(WebRTC));
function receivedSdp(action$, store) {
return action$.ofType(VideoStream.RECEIVED_SDP)
.mergeMap(action => {
const {peerConnection} = store.getState().videoStreams;
const {sdp} = action.payload;
return setRemoteSdpObservable(peerConnection)
.map(result => myReducer.myAction());
})
};
You could use this to create Observable wrappers for all the WebRTC apis.
So the problem is that setRemoteSdp doesn't return an Observable while myReducer.myAction() does and that's the Observable you want to merge?
You can use Observable.create and wrap the WebRTC.setRemoteSdp call:
.mergeMap(action => {
return Observable.create(observer => {
WebRTC.setRemoteSdp(peerConnection, sdp, () => {
observer.next(myReducer.myAction());
observer.complete();
})
});
}
.mergeAll()
The Observable.create returns an Observable that emits another Observable from myReducer.myAction(). Now I have in fact so-called higher-order that I want to flatten using mergeAll() (concatAll would work as well).
I recently started migrating things from jQ to a more structured framework being VueJS, and I love it!
Conceptually, Vuex has been a bit of a paradigm shift for me, but I'm confident I know what its all about now, and totally get it! But there exist a few little grey areas, mostly from an implementation standpoint.
This one I feel is good by design, but don't know if it contradicts the Vuex cycle of uni-directional data flow.
Basically, is it considered good practice to return a promise(-like) object from an action? I treat these as async wrappers, with states of failure and the like, so seems like a good fit to return a promise. Contrarily mutators just change things, and are the pure structures within a store/module.
actions in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.
Here is an example: myAction returns a Promise, makes a http call and resolves or rejects the Promise later - all asynchronously
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
// Do something here... lets say, a http call using vue-resource
this.$http("/api/something").then(response => {
// http success, call the mutator and change something in state
resolve(response); // Let the calling function know that http is done. You may send some data back
}, error => {
// http failed, let the calling function know that action did not work out
reject(error);
})
})
}
}
Now, when your Vue component initiates myAction, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:
export default {
mounted: function() {
// This component just got created. Lets fetch some data here using an action
this.$store.dispatch("myAction").then(response => {
console.log("Got some data, now lets show something in this component")
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
})
}
}
As you can see above, it is highly beneficial for actions to return a Promise. Otherwise there is no way for the action initiator to know what is happening and when things are stable enough to show something on the user interface.
And a last note regarding mutators - as you rightly pointed out, they are synchronous. They change stuff in the state, and are usually called from actions. There is no need to mix Promises with mutators, as the actions handle that part.
Edit: My views on the Vuex cycle of uni-directional data flow:
If you access data like this.$store.state["your data key"] in your components, then the data flow is uni-directional.
The promise from action is only to let the component know that action is complete.
The component may either take data from promise resolve function in the above example (not uni-directional, therefore not recommended), or directly from $store.state["your data key"] which is unidirectional and follows the vuex data lifecycle.
The above paragraph assumes your mutator uses Vue.set(state, "your data key", http_data), once the http call is completed in your action.
Just for an information on a closed topic:
you don’t have to create a promise, axios returns one itself:
Ref: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4
Example:
export const loginForm = ({ commit }, data) => {
return axios
.post('http://localhost:8000/api/login', data)
.then((response) => {
commit('logUserIn', response.data);
})
.catch((error) => {
commit('unAuthorisedUser', { error:error.response.data });
})
}
Another example:
addEmployee({ commit, state }) {
return insertEmployee(state.employee)
.then(result => {
commit('setEmployee', result.data);
return result.data; // resolve
})
.catch(err => {
throw err.response.data; // reject
})
}
Another example with async-await
async getUser({ commit }) {
try {
const currentUser = await axios.get('/user/current')
commit('setUser', currentUser)
return currentUser
} catch (err) {
commit('setUser', null)
throw 'Unable to fetch current user'
}
},
Actions
ADD_PRODUCT : (context,product) => {
return Axios.post(uri, product).then((response) => {
if (response.status === 'success') {
context.commit('SET_PRODUCT',response.data.data)
}
return response.data
});
});
Component
this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
if (res.status === 'success') {
// write your success actions here....
} else {
// write your error actions here...
}
})
TL:DR; return promises from you actions only when necessary, but DRY chaining the same actions.
For a long time I also though that returning actions contradicts the Vuex cycle of uni-directional data flow.
But, there are EDGE CASES where returning a promise from your actions might be "necessary".
Imagine a situation where an action can be triggered from 2 different components, and each handles the failure case differently.
In that case, one would need to pass the caller component as a parameter to set different flags in the store.
Dumb example
Page where the user can edit the username in navbar and in /profile page (which contains the navbar). Both trigger an action "change username", which is asynchronous.
If the promise fails, the page should only display an error in the component the user was trying to change the username from.
Of course it is a dumb example, but I don't see a way to solve this issue without duplicating code and making the same call in 2 different actions.
actions.js
const axios = require('axios');
const types = require('./types');
export const actions = {
GET_CONTENT({commit}){
axios.get(`${URL}`)
.then(doc =>{
const content = doc.data;
commit(types.SET_CONTENT , content);
setTimeout(() =>{
commit(types.IS_LOADING , false);
} , 1000);
}).catch(err =>{
console.log(err);
});
},
}
home.vue
<script>
import {value , onCreated} from "vue-function-api";
import {useState, useStore} from "#u3u/vue-hooks";
export default {
name: 'home',
setup(){
const store = useStore();
const state = {
...useState(["content" , "isLoading"])
};
onCreated(() =>{
store.value.dispatch("GET_CONTENT" );
});
return{
...state,
}
}
};
</script>
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.