I'd like to unit test some logic in my directive :
...
link: function ($scope, $element) {
var rightMargin = 3;
var w = $element.find('span')[0].scrollWidth;
if (w > 100) {
$element.css('width', (w + rightMargin) + 'px');
}
...
Because the $compile service doesn't really add elements on document, scrollWidth always returns 0.
So I don't know how to stub the returned value of $element.find call because there is no way to access $element instance in my unit test.
Try setting the HTML of the page directly inside of body
Using innerHTML:
angular.element(document.body).html('<div class="my-elm" my-directive></div>');
var elm = angular.element(document.body.querySelector('.my-elm'));
$compile(elm)($rootScope);
Or using append:
var elm = angular.element('<div class="my-elm" my-directive></div>');
angular.element(document.body).append(elm);
$compile(elm)($rootScope);
If this is a unit test, then you shouldn't be testing the external dependencies, only that you're logic is accurate. I'd stub it out like.
spyOn($element,'css');
#call your code here
expect($element.css).toHaveBeenCalledWith('width','99px');
Related
Man, this firebase unit testing is really kicking my butt.
I've gone through the documentation and read through the examples that they provide, and have gotten some of my more basic Firebase functions unit tested, but I keep running into problems where I'm not sure how to verify that the transactionUpdated function passed along to the refs .transaction is correctly updating the current object.
My struggle is probably best illustrated with their child-count sample code and a poor attempt I made at writing a unit test for it.
Let's say my function that I want to unit test does the following (taken straight from that above link):
// count.js
exports.countlikechange = functions.database.ref('/posts/{postid}/likes/{likeid}').onWrite(event => {
const collectionRef = event.data.ref.parent;
const countRef = collectionRef.parent.child('likes_count');
// ANNOTATION: I want to verify the `current` value is incremented
return countRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
}
else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
}).then(() => {
console.log('Counter updated.');
});
});
Unit Test Code:
const chai = require('chai');
const chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
const assert = chai.assert;
const sinon = require('sinon');
describe('Cloud Functions', () => {
let myFunctions, functions;
before(() => {
functions = require('firebase-functions');
myFunctions = require('../count.js');
});
describe('countlikechange', () => {
it('should increase /posts/{postid}/likes/likes_count', () => {
const event = {
// DeltaSnapshot(app: firebase.app.App, adminApp: firebase.app.App, data: any, delta: any, path?: string);
data: new functions.database.DeltaSnapshot(null, null, null, true)
}
const startingValue = 11
const expectedValue = 12
// Below code is misunderstood piece. How do I pass along `startingValue` to the callback param of transaction
// in the `countlikechange` function, and spy on the return value to assert that it is equal to `expectedValue`?
// `yield` is almost definitely not the right thing to do, but I'm not quite sure where to go.
// How can I go about "spying" on the result of a stub,
// since the stub replaces the original function?
// I suspect that `sinon.spy()` has something to do with the answer, but when I try to pass along `sinon.spy()` as the yields arg, i get errors and the `spy.firstCall` is always null.
const transactionStub = sinon.stub().yields(startingValue).returns(Promise.resolve(true))
const childStub = sinon.stub().withArgs('likes_count').returns({
transaction: transactionStub
})
const refStub = sinon.stub().returns({ parent: { child: childStub }})
Object.defineProperty(event.data, 'ref', { get: refStub })
assert.eventually.equals(myFunctions.countlikechange(event), true)
})
})
})
I annotated the source code above with my question, but I'll reiterate it here.
How can I verify that the transactionUpdate callback, passed to the transaction stub, will take my startingValue and mutate it to expectedValue and then allow me to observe that change and assert that it happened.
This is probably a very simple problem with an obvious solution, but I'm very new to testing JS code where everything has to be stubbed, so it's a bit of a learning curve... Any help is appreciated.
I agree that unit testing in the Firebase ecosystem isn't as easy as we'd like it to be. The team is aware of it, and we're working to make things better! Fortunately, there are some good ways forward for you right now!
I suggest taking a look at this Cloud Functions demo that we've just published. In that example we use TypeScript, but this'll all work in JavaScript too.
In the src directory you'll notice we've split out the logic into three files: index.ts has the entry-logic, saythat.ts has our main business-logic, and db.ts is a thin abstraction layer around the Firebase Realtime Database. We unit-test only saythat.ts; we've intentionally kept index.ts and db.ts really simple.
In the spec directory we have the unit tests; take a look at index.spec.ts. The trick that you're looking for: we use mock-require to mock out the entire src/db.ts file and replace it with spec/fake-db.ts. Instead of writing to the real database, we now store our performed operations in-memory, where our unit test can check that they look correct. A concrete example is our score field, which is updated in a transaction. By mocking the database, our unit test to check that that's done correctly is a single line of code.
I hope that helps you do your testing!
Problem
I am testing a directive. The directive has a watch on a scope variable, but the watch is never called. Putting the $watch call directly inside the unit test works. Why isn't the $watch in the directive not called?
Thanks!
Details
Here's my original test. The directive has a watch in it, looking at 'reEvalCreateDate'. It's never called and the date string is always empty.
Test with no watch statement in it
it('should inject a date string', function()
{
scope.reEvalCreateDate = reEvalCreateDateAsNumber;
expect(elm.text()).toBe('');
scope.$digest();
expect(elm.text()).toBe(new Date(reEvalCreateDateAsNumber).toUTCString());
});
As a test, I put the watch within the unit test. It is correctly called and the test passes.
Source (modified to put the watch in the test itself)
it('should inject a date string', function()
{
scope.$watch('reEvalCreateDate', function(newValue, oldValue)
{
var date = new Date(newValue);
scope.dateString = date.toUTCString();
dump(scope.dateString)
});
scope.reEvalCreateDate = reEvalCreateDateAsNumber;
expect(elm.text()).toBe('');
scope.$digest();
expect(elm.text()).toBe(new Date(reEvalCreateDateAsNumber).toUTCString()); //This passes!
});
Directive Source Code
Here's the directive code with the watch statement in it.
link: function(scope, element, attrs)
{
scope.$watch('show',function(shouldShow){
console.log('should show reeval timer? ' + shouldShow);
if(shouldShow) {
$(element).fadeIn('slow');
}
else if(shouldShow === false){
$(element).fadeOut('slow');
}
});
//this statement's never called when testing!!
scope.$watch('reEvalCreateDate', function(newValue, oldValue)
{
var date = new Date(newValue);
scope.dateString = date.toUTCString();
});
}
Setup Code
Here's all the init code for the test.
var elm, scope;
var reEvalCreateDateAsNumber = 1359487598000;
beforeEach(inject(function($rootScope, $compile)
{
// we might move this tpl into an html file as well...
elm = angular.element(
'<reeval-timer show="showTimePopup" re-eval-create-date="reEvalCreateDate" class="re-eval-timer" ng-cloak >' +
'{{dateString}}' +
'</reeval-timer>');
scope = $rootScope.$new();
$compile(elm)(scope);
scope.$digest();
}));
Update
Per the comment, I don't think that I am setting the scope on the directive; I don't see anything on the API that allows me to do that. I've tried several permutations of the $compile call, attempting to set the scope on it:
create an element from html, pass it to compile. No luck.
elm = angular.element(
'<div>' +
'<tabs>' +
'<pane title="First Tab">' +
'first content is {{first}}' +
'</pane>' +
'<pane title="Second Tab">' +
'second content is {{second}}' +
'</pane>' +
'</tabs>' +
'</div>');
scope = $rootScope;
$compile(elm)(scope);
and by creating an element first
var element = $compile('<p>{{total}}</p>')(scope);
If the issue is that I'm just not setting the scope on the directive, the error would likely be in my beforeEach, I think. Here it is:
describe('Reeval Timer', function()
{
var elm, scope,element;
var reEvalCreateDateAsNumber = 1359487598000;
beforeEach(inject(function($rootScope, $compile)
{
// we might move this tpl into an html file as well...
elm = angular.element(
'<reeval-timer show="showTimePopup" re-eval-create-date="reEvalCreateDate" class="re-eval-timer" ng-cloak >' +
'</reeval-timer>');
scope = $rootScope.$new();
element = $compile(elm.contents())(scope);
scope.$digest();
}));
....
I just ran into a similar issue while testing a directive with isolate scope.
The reason it wasn't called is that $digest "processes all of the watchers of the current scope and its children" (http://docs.angularjs.org/api/ng.$rootScope.Scope). Now, you would think that a directive's bi-directionally bound variables (defined with "=") would be watching the test's scope, but I found that my $watch wasn't being called. Is this a bug in angular?
My solution was to wrap all the testing code after the scope.$digest() into a $timeout, e.g:
inject(function($timeout) {
// change test scope variable
scope.$digest();
$timeout(function() {
// assertions
expect(...)
}, 0);
}
Edit: Also see Unit testing an AngularJS directive that watches an an attribute with isolate scope
I was experiencing what seems like the same problem. In my case, my directive had a templateUrl set rather than a hardcoded template, so in my unit test I also had to set up a mocked http request by calling:
$httpBackend.expectGET('/template.html').respond('<div></div>');
Everything else looked the same as your example, and my watch wasn't being called.
Then I remembered when using $httpBackend for mocking the request, you have to call $httpBackend.flush() like so:
$compile(elem)(scope);
$httpBackend.flush();
As soon as the flush call was hit, my watch was called.
Hope this helps....
Jason
I have an object that reads data from an Excel file using, which takes a IDbConnection, IDbDataAdapter and an IDbCommand. I use the adapters fill method to populate a table with data, and this is how I am currently mocking it:
[TestCase]
public void TestReadCellsFromSpreadsheetReadsSuccessfully()
{
var cells = new List<ReportData>
{
new ReportData { CellId = 1, ExcelCellLocation = "A1"},
new ReportData { CellId = 2, ExcelCellLocation = "A2"},
new ReportData { CellId = 3, ExcelCellLocation = "A3"},
new ReportData { CellId = 4, ExcelCellLocation = "A4"}
};
_mockAdapter.Setup(a => a.Fill(It.IsAny<DataSet>()))
.Callback((DataSet ds) =>
{
if (ds.Tables["Table"] == null)
{
ds.Tables.Add("Table");
ds.Tables["Table"].Columns.Add(new DataColumn());
}
var row = ds.Tables["Table"].NewRow();
row[0] = "Test";
ds.Tables["Table"].Rows.Add(row);
});
var excelReader = new ExcelReader(_mockConnection.Object, _mockAdapter.Object, _mockCommand.Object);
excelReader.ReadCellsFromSpreadsheet("Deal Summary", cells);
_mockCommand.VerifySet(c => c.CommandText = It.IsAny<string>(), Times.Exactly(cells.Count));
_mockAdapter.VerifySet(a => a.SelectCommand = _mockCommand.Object, Times.Exactly(cells.Count));
_mockAdapter.Verify(a => a.Fill(It.IsAny<DataSet>()), Times.Exactly(cells.Count));
}
This implementation works, but I feel like I'm doing too much to Mock the adapter... is there a better way to do this?
Do not pass those 3 objects as parameters. Instead pass IDataReader, IDataProvider or sth like that that returns data. Then You just mock this object. And you don't need reference to System.Data in project containing ExcellReader.
And two other things I don't like about your code.
Why TestCase instead of Test?
Are you sure you want to create command and fill dataset for each column separately? (but maybe I don't understand your code)
In general, I have some rules about data access:
Write a simple class that wraps all the data access logic, so that other classes don't have to deal with DataAdapters and all that crap.
When you write unit tests, don't mock out DataAdapters; instead just mock out the wrapper classes that you just created.
Make the data access wrapper logic so simple that it doesn't really need to be unit tested. If it DOES need to be tested, then write integration tests that hit a small sample database.
I've had a bug that has been bugging me for days. I'm pretty new to Node and the Jade templating system so bear with me: I'm looking to add stylesheets in the following way:
App.js (Express):
app.get('/', loadUser, function(req, res) {
var User = req.user;
// console.log(User.groups[2]);
// var groups = User.groups.split(',');
// OK DUh. This only gets called when the client has the script Socket.IO
// and client runs socket.connect()
getMessages(User, function(messages) {
var locals = {
scripts: [
'https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js',
'index.js'
],
stylesheets: [
'index.css'
],
user : User,
messages: messages
};
console.log('ok');
res.render('app.jade', {locals : locals});
});
});
In layout.jade (which is executed with app.jade) I have:
!!! 5
html
head
title UI
link(rel='stylesheet', href = 'stylesheets/reset.css')
link(rel='stylesheet', href = 'stylesheets/layout.css')
- var stylesheets = stylesheets || [];
#{stylesheets}
- each stylesheet in stylesheets
- if(stylesheet.indexOf('http') >= 0)
link(rel='stylesheet', href = stylesheet)
- else
link(rel='stylesheet', href = "stylesheets/"+stylesheet )
Plus more... I keep running into the same error:
9. ' - if(stylesheet.indexOf(\'http') >= 0)'
Object function () {
var o = {}, i, l = this.length, r = [];
for(i=0; i
for(i in o) r.push(o[i]);
return r;
} has no method 'indexOf'
Now.. the gotcha is that this exact template works in another application that passes in the exact same variables: I would REALLY appreciate any suggestions you guys have on this thorny issue!
Thanks!
Matt Mueller
So here's your issue...
in this line:
res.render('app.jade', {locals : locals});
you are passing in locals ==> locals, which is a hash (ok, so I'm a PERL guy, I think JS calls them 'associative arrays')
So now inside your jade template we have the line:
- var stylesheets = stylesheets || [];
inside JADE, you have defined the variable "locals", but everything else is hidden under that, so the variable "stylesheets" is NOT defined (locals.stylesheets is defined instead). So this line of code sets the variable "stylesheets" to "[]"
So here's where I have to speculate. "indexOf" is a method of the Array object. Perhaps arrays constructed inside JADE don't have this method whereas arrays constructed in node.js DO have this method. Which would explain why you get an error trying to call "stylesheets.indexOf(...)"
Another day , another question. My service layer has the following method
public MatchViewData CreateMatch(string user)
{
var matchViewData = !HasReachedMaxNumberOfMatchesLimit(user) ?
CreateMatchAndAddToRepository(user) :
MatchViewData.NewInstance(new Match(user));
matchViewData.LimitReached = HasReachedMaxNumberOfMatchesLimit(user);
return matchViewData;
}
The method calls the this helper method to create a new match object:
private MatchViewData CreateMatchAndAddToRepository(string user)
{
var match = new Match(user);
MatchRepository.Add(match);
return MatchViewData.NewInstance(match);
}
The repository stores the given match object and sets the id to some value > 0.
public void Add(Match match)
{
Check.Require(match != null);
var numberOfMatchesBefore = Matches.Count;
SetIdPerReflection(match, NextVal());
Matches.Add(match);
Check.Ensure(numberOfMatchesBefore == Matches.Count - 1);
}
The matchviewdata object copies some properties of the the match object (including the id).
My unit test should verify that the resulting viewdata object in the service has an id > 0. To archieve this, i have to mock the repository and the behaviour of the add method. But the service method creates a new match object every time its been called and the add method on the repository updates the referenced match object (there is no need for a return value). I have no idea to solve this with moq.
This is my unit test so far:
[Test]
public void ServiceCreateMatchReturnedMatchViewDataHasNonZeroId()
{
var match = TestUtils.FakePersistentMatch(User, 1);
var repositoryMock = new Mock<IMatchRepository>();
repositoryMock.Setup(
r => r.Add(It.IsAny<Match>())).Callback(() => match.Id = 1);
var serviceFacade = new DefaultServiceFacade(repositoryMock.Object);
var returnedMatch = serviceFacade.CreateMatch(User);
Assert.That(returnedMatch.Id, Is.GreaterThan(0));
}
I tried some other variations - nothing works.
It looks to me your problem is in this line;
repositoryMock.Setup(
r => r.Add(It.IsAny<Match>())).Callback(() => match.Id = 1);
What you're actually doing here is setting the id of the first match object you have declared in your test, NOT the new match created in your service.
Because the Match object you will be supplying to the Repository is created internally, I can't think of an easy way to reference it in your Test method to setup a callback for it. To me, this is a sign you may be trying to test too much in one unit test.
I think you should simply test that the Add method is called and write a separate test to ensure that it works as exepected.
I propose something like this;
[Test]
public void ServiceAddsNewMatchToRepository()
{
var repositoryMock = new Mock<IMatchRepository>();
bool addCalled = false;
repositoryMock
.Expect(r => r.Add(It.Is<Match>(x => x.Id == 0))
.Callback(() => addCalled = true);
var serviceFacade = new DefaultServiceFacade(repositoryMock.Object);
serviceFacade.CreateMatch(User);
Assert.True(addCalled);
}
....
[Test]
public void AddingANewMatchGeneratesANewId()
{
var match = new Match(user);
var matchRepository = new MatchRepository();
var returnedMatch = matchRepository.Add(match);
Assert.That(returnedMatch.Id, Is.GreaterThan(0));
}