I am using a 3rd party library for logging some events, setup in my component:
In component componentDidMount:
this.evenStream = new thirdPartyLib();
this.evenStream.setArgs(args);
this.evenStream.addQueue('the'); // should be 'the-spell' for misspell page
const event1 = new Event1();
const event2 = new Event2();
this.evenStream.addQueue(event1); // adds events in sequence in queue
this.evenStream.addQueue(event2); // adds events in sequence in queue
this.evenStream.reportEvents(); // reports events previously added to queue, once reported the queue is cleared
In test:
this.wrapper = mount(<component />); // looks good
this.evenStreamInstance = this.wrapper.instance().evenStream; //returns properties of evenStream
this.eventStub = sinon.stub(this.evenStreamInstance, 'reportEvents');
expect(this.wrapper).to.be.ok; // looks good
expect(this.evenStreamInstance).to.exist; // looks good
expect(this.eventStub).to.have.been.called; // returns error
AssertionError: expected reportEvents to have been called at least once, but it was never called
I am sure the events are being logged correctly, I've checked the stream and all looks good, am not sure how to test it. Ideally I would like to test what all events are added to addQueue and then get reported but I am not able to capture that information.
Any help is appreciated. Thanks.
Update
I tried again as per this post with stub, but still getting same error
const eventStub = sinon.stub(new thirdPartyLib());
this.wrapper = mount(<component evenStream={eventStub} />); //looks good
this.evenStreamInstance = this.wrapper.instance().evenStream; //returns properties of evenStream
this.evenStreamInstance.reportEvents = sinon.spy(this.evenStreamInstance.reportEvents);
expect(this.wrapper).to.be.ok; // looks good
expect(this.evenStreamInstance).to.exist; //looks good
this.wrapper.update();
expect(this.evenStreamInstance.reportEvents).to.have.been.calledOnce; // gives error
AssertionError: expected reportEvents to have been called exactly once, but it was called 0 times
Related
I have a react state
const [list, setList] = useState([])
and a react effect that is triggered when the list is modified, and do some work with the first element in the list:
useEffect( () => {
if(list.length <= 0) return
//Do something with the first element of the list
//Remove first element of the list
}, [list])
In this way, the effect trigger itself n times where n is the number of the element in the list.
Then i have another method in my component that insert elements in the list with the setList() method, let's call it
insertElemInList = () => {
//insert one or more elements in list
}
when insertElemInList is called, the useEffect trigger and start working for n times.
I don't know how many times the insertElemInList() is called, and how many elements is inserted every time, since this method is called after some actions of the user on the page.
So if an user call the insertElemInList() two or more times, before the last iteration of the effect is finished, then the effect trigger in the wrong way, in fact it will activate due to the change of state given by insertElemInList, but also by itself, resulting in more iterations and wrong behaviour.
So i'm trying to figure out how to use something inside the effect that doesn't trigger the effect itself, but can be used correctly.
for example I was thinking of modifying the effect and the state adding
const [semWait, setSem] = useState(1)
and then, continue to update the list state with the insertElemInList() method, but now:
useEffect( () => {
let doSomething = () => {
if(list.length < 0) return
//Do Something with the first element of the list
//Remove first element from the list
if(list.length > 0) doSomething()
}
doSomething()
setSem(1)
}, [semWait])
insertElemInList = () => {
//insert one or more elements in list
if(semWait == 1) setSem(0)
}
the above code is just an example of how I can solve the problem, I don't think it is the best solution and I gave you this example just to make you understand what I would like to do.
however, as you can see in this way I could add as many value as i want to my state whith insertElemInList() and trigger the effect only if it is not already active (in other word, only if the semaphore is reset by the effect itself). However, I know it's not a good thing to use a state in the effect, without including it in dependencies, and if i add the state list as dependency of the useEffect the problem return.
the problem is that I can't figure out how to use a value inside useEffect without including it in the dependency
EDIT:
sorry for the late reply, i tried to implement this code on my own but there are workflow problems in my work, i'll try to explain the problems:
the code is a snippet to download some file from an API, the user on the site have a list of files to download, he can click on the files to download them as many times as he wants. my intent is to create a request queue, so as not to send too many requests to the server.
the code below show my work, i've inserted some comments to let you figure out how my code should work:
const [queue, setQueue] = useState({
"op_name": "NO_OP"
})
//file download function
let requestFileDownload = (fileId) => {
/*
This function construct the object to put in queue state and call the method 'insertInQueue'
*/
let workState = appState
insertInQueue({
"op_name": "DOWNLOAD_FILE",
"file_id": fileId,
"username": workState.user.username,
"token": workState.user.token
})
}
//Function insertInQueue to insert an element in the queue
let insertInQueue = (objQueue) => {
//Some control to check if the object passed exist, and have valid fields
if (!objQueue || !objQueue.op_name || objQueue.op_name === "NO_OP") return //nothing to insert in queue
//calling method to insert in timeline div, this work only whith front-end dom elements (full synchronous)
insertElemInTimeline(objQueue.op_name)
//setting timeout in which try to insert the object passed in queue
setTimeout(function run() {
let workQueue = queue //gettind queue object
if (workQueue && workQueue.op_name === "NO_OP") {
/*
if queue exist and the op_name is "NO_OP", this mean that the previus operations on the queue
is finished, so we can start this operation
*/
setQueue(objQueue) //set the queue with the object passed as paramether to trigger the effect
return;
}
// if the queue op_name was != "NO_OP" call the function again for retry to insert in 1 second
setTimeout(run, 1000)
}, 0)
}
//Effect triggered when queue object change
useEffect(() => {
if (queue.op_name === "NO_OP") return //no operation to do
//Effective file download
let downloadFileEffect = async () => {
let objQueue = queue //getting queue state
//Two functions to download the element by calling backend api
let downloadFileResponse = await downloadFile(objQueue.file_id, objQueue.username, objQueue.token)
download(downloadFileResponse.data, downloadFileResponse.headers['x-suggested-filename'])
//after the method have completed, i can set a new state for the queue with "op_name": "NO_OP"
let appoStateQueue = {
"op_name": "NO_OP"
}
setQueue(appoStateQueue)
//method for remove the element from the dom
removeElemFromTimeline()
}
//calling function to trigger the donwload.
downloadFileEffect()
}, [queue])
now the problem is, that when i try to reset the queue state in the effect, when i call:
let appoStateQueue = {
"op_name": "NO_OP"
}
setQueue(appoStateQueue)
the queue is not resetted in the case the user have clicked two download one after the first is running.
In fact the queue stops with the first object inserted in it, and is not reset by the effect, so the second download never starts, because it sees forever the queue occupied by the first download.
In case user click one download, then wait for the download, and only then click the second, then there's no problem, and the queue is resetted correctly by the effect
First, useEffect doesn't run for "trigger itself n times where n is the number of the element in the list". useEffect will run every time list changes in length or resides in a different memory space than it did in a previous render. This is how "shallow" comparison works with javascript objects in react. Your main issue is that you are changing your dependency from within the effect. This means that while the effect runs, it updates the dependency and forces it to run again and again and again...memory leak!
Your solution might work, but as you stated is not best practice. A better solution (imo) would be to allow for a "parsedList" state that can be the end result of parsing the list. Let the source of truth with the list only be impacted by the client interaction. You monitor these changes and change your parsedList based on these changes.
I have a directive with custom debounce decorators. The directive is a fairly simple that listen on "scroll" event.
export class MyScrollDirective {
#Output() scrolled = new EventEmitter<any>();
#HostListener('scroll', ['$event'])
//debouce is added to prevent multiple call when scroll reach the end
//in the if statement below
#myDebounce(300)
onScroll(event) {
this.scrolled.emit(null);
}
}
In my test, I have an assumption that this.scrolled.emit() will be called whenever onScroll is called. Obviously this is not the case.
With the debounce, it looks like onScroll can be called multiple times from spyOn while this.scrolled only emit once. For example, if I trigger scroll events 2 times in my test within 300ms intervals, the onScroll is called 2 time while the this.scrolled has only 1 emit. The 1 emit is of course desired behavior, but how come onScroll is called twice. When the app is actually running in browser - instead of tests - the onScroll is actually called only once.
Why is this?!
The test detail can be view at StackBliz.io
This is a very interesting question!
In order to get a better understanding of how things work, I'd recommend opening this forked StackBlitz and place these breakpoints:
jasmine.js
calls.push(context); - line 2162
{ - line 5972
myDebounceDecorator.ts
descriptor.value - line 16
var params = []; - line 18
dummy.component.spec.ts
vsCss.triggerEventHandler(...) - line 36
vsCss.triggerEventHandler(...) - line 40
Note: I've used Firefox Dev Tools
This is what happens after you refresh the app:
descriptor.value; is reached; this means that the spy will be applied on the newly created function:
descriptor.value = function () { /* this will be spied */ }
vsCss.triggerEventHandle (line 36) is reached
var callData = { is reached, because of the spy.
calls.push(context); is reached, which means the spy(onScroll) was called
var params = []; is reached, because the spy was defined with callThrough(), which means that the original implementation will be used
vsCss.triggerEventHandler (line 40) is reached
var callData = { is reached, because of the spy
calls.push(context); is reached again; if we were to hover over calls, we'd see that it already has an element(from the previous vsCss.triggerEventHandle), hence the calls' length will be 2
var params = []; - the original implementation is used
var callData = { is reached; this time, it's the spy what we used on the Subject(i.e EventEmitter); we can be sure of this by hovering over this from object: this
calls.push(context); - calls belongs to the Subject's spied method
var callData = { is reached; this time, the spy belongs to onDummy method
calls.push(context); - onDummy's spy
I am trying to ensure that the right value is copied to the users clipboard when they click a button. This is my copy method. I am using a ref on the input to access the right value.
protected copyToClipboard() {
console.log("clicked!");
const text = this.controls.copyData;
if (!_.isNil(text)) {
text.current.focus();
text.current.select();
document.execCommand("copy");
this.setState({copied: true});
}
}
For my test:
test("Ensure right value is copied to clipboard", () => {
const wrapper = mount(<MyComponent />);
const copyButton = wrapper.find(".copyBtn");
copyButton.simulate("click");
const copyToClipboardSpy = jest.spyOn(document as any, "execCommand");
wrapper.update();
expect(copyToClipboardSpy).toHaveBeenCalledWith("copy");
});
The error I receive when I run the test is TypeError: document.execCommand is not a function which makes sense, but I am unsure how to approach this.
I am relatively new to testing, just to put that out there. I also have read that I may not be able to access the document.execCommand but have struggled to find a good alternative to hijack the test and access the value being copied. I appreciate any advice that can be given on the matter!
Posting this in case anyone else was in a similar boat. It's doesn't necessarily check the value yet, but one piece I managed was with the document.execCommand method.
I set up a mock function above the wrapper:
document.execCommand = jest.fn();
With this, the test stopped throwing the TypeError. Then my expectations included checking for the spy to have been called, expect my copy state to have changed to true, and:
expect(document.execCommand).toHaveBeenCalledWith("copy");
Test passes! A possible solution for the value is to see if I can "paste" the value and then check it. Will edit this response if/when I can manage that
When you use navigator.clipBoard.writeText instead of using document.exec("copy"), you can refer to this thread for an elegant solution that lets you assert on the content as well.
Being execCommand no longer an option as it is deprecated (see MDN), you should be using navigator.clipboard.writeText('your copied data');.
To mock navigator.clipboard, you could do the following:
// It's important to keep a copy, so your tests don't bleed
const originalClipboard = navigator.clipboard;
const mockedWriteText = jest.fn();
navigator.clipboard = {
writeText: mockedWriteText,
};
const copyComponent = await screen.findByTestId('copy-component');
await userEvent.click(copyComponent);
expect(mockedWriteText).toHaveBeenCalledTimes(1);
expect(mockedWriteText).toHaveBeenCalledWith('your copied data');
// Remember to restore the original clipboard
navigator.clipboard = originalClipboard;
jest.resetAllMocks();
You can also do Object.assignProperty instead of directly modifying the navigator object.
This snippet assumes you are using React Testing Library with User Event.
Tying to learn how use Akka.net Streams to process items in parallel from a Source.Queue, with the processing done in an Actor.
I've been able to get it to work with calling a function with Sink.ForEachParallel, and it works as expected.
Is it possible to process items in parallel with Sink.ActorRefWithAck (as I would prefer it utilize back-pressure)?
About to press Post, when tried to combine previous attempts and viola!
Previous attempts with ForEachParallel failed when I tried to create the actor within, but couldn't do so in an async function. If I use an single actor previous declared, then the Tell would work, but I couldn't get the parallelism I desired.
I got it to work with a router with roundrobin configuration.
var props = new RoundRobinPool(5).Props(Props.Create<MyActor>());
var actor = Context.ActorOf(props);
flow = Source.Queue<Element>(2000,OverflowStrategy.Backpressure)
.Select(x => {
return new Wrapper() { Element = x, Request = ++cnt };
})
.To(Sink.ForEachParallel<Wrapper>(5, (s) => { actor.Tell(s); }))
.Run(materializer);
The Request ++cnt is for console output to verify the requests are being processed as desired.
MyActor has a long delay on every 10th request to verify the backpressure was working.
I am facing intermittent protractor test failures across a range of test suites without any real pattern in the fail cases to indicate what could be going on, for example it's not the same tests that are failing. Sometimes I will get many failures and on other occasions just a single fail case.
I should point out that this tends to only happen when performing test runs on a Jenkins CI server we have configured (running under linux). Locally on Windows dev machines we may get a single fail case after 30-40 runs which I can live with!
The application we are testing is currently built with angular 1.5 and we are using angular material 1.1.3
Due to the animations used in angular material and the performance hit these can have, we have already tried disabling animations following this approach here which certainly make the tests quicker but dont help with the fail cases we are seeing/
I am at a point now where I am running one test suite over and over, after 5 successful runs it then failed on it's 6th attempt on our Jenkins CI environment\linux box, locally I have run this test many times now and no failures yet.
The test suite in question is detailed below along with a page object file snippet:
//test suite
describe('Operators View', function () {
var operatorPage = require('./operators.po.js'),
loginView = require('../login/login.po.js'),
page = new operatorPage();
describe('Large screen tests', function () {
beforeAll(function () {
loginView.login();
});
afterAll(function () {
loginView.logout();
});
it('should create an operator', function () {
page.settlementBtn.click();
page.operatorsBtn.click();
page.fabBtn.click();
page.createOperator();
expect(page.headline.getText()).toEqual('Operators');
});
});
});
// operators.po.js
var operatorsSection = function() {
this.helper = new Helpers();
this.headline = element(by.css('.md-headline'));
this.settlementBtn = element(by.css('[ui-sref="settlement"]'));
this.operatorsBtn = element(by.css('[ui-sref="operators"]'));
this.fabBtn = element(by.css('.md-fab'));
// Form Elements
this.licenceNumber = element(by.model('vm.transportOperator.licenceNumber'));
this.tradingName = element(by.model('vm.tradingName'));
this.name = element(by.model('vm.name'));
this.operatorAddressFirstLine = element(by.model('vm.transportOperator.address.line1'));
this.operatorAddressCityTown = element(by.model('vm.transportOperator.address.line5'));
this.operatorAddressPostCode = element(by.model('vm.transportOperator.address.postcode'));
this.payeeAddressFirstLine = element(by.model('vm.transportOperator.payee.address.line1'));
this.payeeAddressCityTown = element(by.model('vm.transportOperator.payee.address.line4'));
this.payeeAddressPostCode = element(by.model('vm.transportOperator.payee.address.postcode'));
this.opID = element(by.model('vm.transportOperator.fields.opID'));
this.spID = element(by.model('vm.transportOperator.fields.spID'));
this.schemeSelect = element(by.model('reference.scheme'));
this.schemeOptions = element(by.exactRepeater('scheme in vm.schemes').row('0'));
this.alias = element(by.model('reference.alias'));
this.reference = element(by.model('reference.reference'));
this.saveBtn = element(by.css('.md-raised'));
this.createOperator = function() {
this.licenceNumber.sendKeys(this.helper.getRandomId(10));
this.tradingName.sendKeys('Protractor Trade Name LTD');
this.name.sendKeys('Protractor Trade Name');
this.operatorAddressFirstLine.sendKeys('Protractor Town');
this.operatorAddressCityTown.sendKeys('Cardiff');
this.operatorAddressPostCode.sendKeys('PT4 4TP');
this.payeeAddressFirstLine.sendKeys('Protractor Town');
this.payeeAddressCityTown.sendKeys('Cardiff');
this.payeeAddressPostCode.sendKeys('PT4 4TP');
this.opID.sendKeys('177');
this.spID.sendKeys('Protractor Spid');
this.schemeSelect.click();
this.schemeOptions.click();
this.alias.sendKeys('PTAlias');
this.reference.sendKeys('Protractor');
this.saveBtn.click();
}
};
module.exports = operatorsSection;
In this test suite after the call to createOperator from the PO file is invoked and the savteBtn is clicked, the application will transition to a state that shows a table of created entries (after successful creation of course). We are using angular ui-router also, currently on version 0.2.18
The expectation fails with:
Expected 'Create An Operator' to equal 'Operators'.
Yet the accompanying screenshot that was captured shows the table view with an 'Operators' heading, it seems the call to page.headline.getText() inside the expectation call is being invoked too soon, so before the database operation to create the item and the page change has had a chance to complete?
I have started wondering if this could be down to the order of promises executed by protractor. I have come across articles talking about control flow in protractor and why there may be occasions when you should hook into the result of a protractor call's promise using .then() - I found this
It got me wondering if I should move the call to my saveBtn.click(), that's called at the end of my page object's createOperator function, into the test suite, so doing something like:
it('should create an operator', function () {
page.settlementBtn.click();
page.operatorsBtn.click();
page.fabBtn.click();
page.createOperator();
page.saveBtn.click().then(function(){
expect(page.headline.getText()).toEqual('Operators');
});
});
I'm starting to clutch at straws here to be honest, so any thoughts\advice from the community here would be much appreciated.
Thanks!
As requested, here is the function I use for waiting for URLs to be as they should.
public waitForUrlToBeLike (urlPart: string, timeout: number = 10000) {
return browser.wait(() => {
return browser.driver.getCurrentUrl().then((url) => {
let regex = new RegExp(urlPart);
return regex.test(url);
});
}, timeout);
}
I also use the following a lot to wait for elements to be present before making assertions on them:
public waitTillPresent (element: ElementFinder, timeout: number = 10000) {
return browser.wait(() => {
return element.isPresent();
}, timeout);
}