How to test web components in Dart? - unit-testing

There isn't much documentation insofar on web-ui testing in Dart. Two methods are available : a) run through Chrome's DumpRenderTree or b) a trick that consists of loading the app as is and running the test code on top of it. For trivial cases, the first option seems to be a bit tedious. So the latter option -- which in my case, doesn't work when it comes to load components.
With the following file structure:
test/
main_test.html
main_test.dart
web/
main.html
app.html
(all the files are listed in this gist)
The following test set hangs on the second step.
main() {
useShadowDom = true;
test('Inline element is initially present.', () {
var story = () => expect(query('#hdr'), isNotNull);
Timer.run(expectAsync0(story));
});
test('Component is loaded.', () {
var story = () => expect(query('#globe'), isNotNull);
Timer.run(expectAsync0(story));
});
}
How could the app component be loaded? More broadly, is there another method of testing web components?

For web-ui test you have to query the shadow dom or the xtag (this) of the webcomponent that you whant to test instead of the "classic" dom.
Based on TodoMVC code sample
With your code:
A working version of this test is :
main() {
useShadowDom = true;
test('Inline element is initially present.', () {
var story = () => expect(query('#hdr'), isNotNull);
Timer.run(expectAsync0(story));
});
test('Component is loaded.', () {
var root = query("span[is=x-app]").shadowRoot;
var story = () => expect(root.query('#globe'), isNotNull);
Timer.run(expectAsync0(story));
});
}
and a test version without expectAsync should be:
main() {
useShadowDom = true;
Timer.run(() {
test('Header element is initially present.', () {
var hdr = query('#hdr');
expect(hdr, isNotNull);
});
test('EchapApp component is loaded.', () {
var root = query("span[is=x-app]").shadowRoot;
var globe = root.query('#globe');
expect(globe, isNotNull);
});
});
}
and finaly a version without shadow dom :
main() {
//useShadowDom = true;
Timer.run(() {
test('Header element is initially present.', () {
var hdr = query('#hdr');
expect(hdr, isNotNull);
});
test('EchapApp component is loaded.', () {
var root = query("span[is=x-app]").xtag;
var globe = root.query('#globe');
expect(globe, isNotNull);
});
});
}
For me this 3 codes are 100% pass on Dartium with
Dart Editor version 0.5.20_r24275
Dart SDK version 0.5.20.4_r24275

You can try using the karma-dart runner: https://github.com/karma-runner/karma-dart
It even has a web components example.
library click_counter_test;
import 'package:unittest/unittest.dart';
import 'dart:html';
import '../web/out/xclickcounter.dart';
main() {
test('CounterComponent.increment', () {
var hello = new DivElement();
var component = new CounterComponent.forElement(hello);
expect(component.count, equals(0));
component.increment();
expect(component.count, equals(1));
});
}

Although it is not Dart specific, you can use Selenium for testing the UI. I believe some members of the Dart team have used Selenium as well to do UI testing.

Related

How to use CodeceptJS to unit-test a JS function

I've set up CodeceptJS for a project and use it to test various end-to-end scenarios.
Now I want to extend the tests-suite to also run unit-tests to verify functionality of custom JS functions.
For example: I have a global object App that has a version attribute. As a first test, I want to confirm that App.version is present and has a value.
My first attempt is a test.js file with the following code:
Feature('Unit Tests');
Scenario('Test App presence', ({ I }) => {
I.amOnPage('/');
I.executeScript(function() {return App.version})
.then(function(value) { I.say(value) } );
});
Problems with this code
The major issue: How can I assert that the App.version is present?
My script can display the value but does not fail if it's missing
My code is very complex for such a simple test.
I'm sure there's a cleaner/faster way to perform that test, right?
Here is a solution that works for me:
Read data from the browser:
I created a custom helper via npx codecept gh and named it BrowserAccess.
The helper function getBrowserData uses this.helpers['Puppeteer'].page.evaluate() to run and return custom code from the browser scope. Documentation for .evaluate()
Custom assertions:
Install the codeceptjs-assert package, e.g. npm i codeceptjs-assert
Add the AssertWrapper-helper to the codecept-config file. This enables checks like I.assert(a, b)
Full Code
codecept.conf.js
exports.config = {
helpers: {
AssertWrapper: {
require: "codeceptjs-assert"
},
BrowserAccess: {
require: './browseraccess_helper.js'
},
...
},
...
}
browseraccess_helper.js
const Helper = require('#codeceptjs/helper');
class BrowserAccess extends Helper {
async getBrowserData(symbolName) {
const currentPage = this.helpers['Puppeteer'].page;
let res;
try {
res = await currentPage.evaluate((evalVar) => {
let res;
try {
res = eval(evalVar);
} catch (e) {
}
return Promise.resolve(res);
}, symbolName);
} catch (err) {
res = null;
}
return res;
}
}
jsapp_test.js (the test is now async)
Feature('Unit Tests');
Scenario('Test App presence', async ({ I }) => {
I.amOnPage('/');
const version = await I.getBrowserData('App.version');
I.assertOk(version);
});

host.json not respected by Visual Studio or web job

I have a queue processing application which is workign fine. I am now trying to persuade the queue trigger to only process one item at a time. My host.json is set up correctly, I think:
But when I run the app (either in Azure as a web job, or locally in Visual Studio), I see this:
I suspect that I am missing something really obvious, so wondering whether anyone has come across this before. I have found a few articles, but nothing that gives me any insight into what I am doing wrong.
Adding the contents of program.cs. I have tried adding "host.json" after the AddAzureAppConfiguration entry, but that makes no difference.
class Program
{
static async Task Main()
{
//var builder = new HostBuilder();
var builder = Host.CreateDefaultBuilder();
builder.ConfigureLogging((context, a) =>
{
a.AddConsole();
});
builder.ConfigureAppConfiguration((hostContext, config) =>
{
config.AddUserSecrets(
"5aa19112-5ff7-467b-b062-f37c3654872d"); // This is automatic for a web app, but not for a console app
var settings = config.Build();
var connectionString = settings.GetConnectionString("AzureAppConfiguration");
config.AddAzureAppConfiguration(connectionString);
});
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
});
builder.ConfigureServices((hostContext, services) =>
{
services.AddMemoryCache();
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
}
I think you are using the Azure WebJobs SDK v3.x. In v3.x, hosts.json does not work for WebJob.
Instead, version 3.x uses the standard ASP.NET Core APIs, so you need to configure it using the ConfigureWebJobs method:
static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage(a => {
a.BatchSize = 8;
a.NewBatchThreshold = 4;
a.MaxDequeueCount = 4;
a.MaxPollingInterval = TimeSpan.FromSeconds(15);
});
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
Docs: https://learn.microsoft.com/pt-pt/azure/app-service/webjobs-sdk-how-to#queue-storage-trigger-configuration

Jasmine unit test spec for my code. I am using Durandal and Knockout

I am very new to Jasmine unit test. I have pasted my code below. But not sure what i am doing wrong inside spec.Can you guys please guide me on the same.
var subscription = self.selectedEmpList.subscribe(function (selectedEmpList) {
if (selectedEmpList) {
dataService.getEmpsByEmpList(self.viewData.EmpId, selectedEmpList.id)
.done(loadEmpSpread);
}
Corresponding Spec what I am trying from whatever I got from googling.
var clickSelectedEmpList = null;
var empListSpy = null;
beforeEach(function () {
fil = new filter();
empListSpy = jasmine.createSpyObj("empListSpy", ["subscribe"]);
});
it('should be activatable', function () {
fil.activate(fixture.filterActivationData);
empListSpy.subscribe.and.callFake(function (selectedEmpList) {
clickSelectedEmpList = selectedEmpList;
});
clickSelectedEmpList();
});
And this is failed with error:-
TypeError: 'undefined' is not an object <evaluating 'empListSpy.subscribe.and.callFake'>

How do I mock an Angular service using jasmine?

This may be a duplicate but I have looked at a lot of other questions here and they usually miss what I am looking for in some way. They mostly talk about a service they created themselves. That I can do and have done. I am trying to override what angular is injecting with my mock. I thought it would be the same but for some reason when I step through the code it is always the angular $cookieStore and not my mock.
I have very limited experience with jasmine and angularjs. I come from a C# background. I usually write unit tests moq (mocking framework for C#). I am use to seeing something like this
[TestClass]
public PageControllerTests
{
private Mock<ICookieStore> mockCookieStore;
private PageController controller;
[TestInitialize]
public void SetUp()
{
mockCookieStore = new Mock<ICookieStore>();
controller = new PageController(mockCookieStore.Object);
}
[TestMethod]
public void GetsCarsFromCookieStore()
{
// Arrange
mockCookieStore.Setup(cs => cs.Get("cars"))
.Return(0);
// Act
controller.SomeMethod();
// Assert
mockCookieStore.VerifyAll();
}
}
I want mock the $cookieStore service which I use in one of my controllers.
app.controller('PageController', ['$scope', '$cookieStore', function($scope, $cookieStore) {
$scope.cars = $cookieStore.get('cars');
if($scope.cars == 0) {
// Do other logic here
.
}
$scope.foo = function() {
.
.
}
}]);
I want to make sure that the $cookieStore.get method is invoked with a 'garage' argument. I also want to be able to control what it gives back. I want it to give back 0 and then my controller must do some other logic.
Here is my test.
describe('Controller: PageController', function () {
var controller,
scope,
cookieStoreSpy;
beforeEach(function () {
cookieStoreSpy = jasmine.createSpyObj('CookieStore', ['get']);
cookieStoreSpy.get.andReturn(function(key) {
switch (key) {
case 'cars':
return 0;
case 'bikes':
return 1;
case 'garage':
return { cars: 0, bikes: 1 };
}
});
module(function($provide) {
$provide.value('$cookieStore', cookieStoreSpy);
});
module('App');
});
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller;
}));
it('Gets car from cookie', function () {
controller('PageController', { $scope: scope });
expect(cookieStoreSpy.get).toHaveBeenCalledWith('cars');
});
});
This is a solution for the discussion we had in my previous answer.
In my controller I'm using $location.path and $location.search. So to overwrite the $location with my mock I did:
locationMock = jasmine.createSpyObj('location', ['path', 'search']);
locationMock.location = "";
locationMock.path.andCallFake(function(path) {
console.log("### Using location set");
if (typeof path != "undefined") {
console.log("### Setting location: " + path);
this.location = path;
}
return this.location;
});
locationMock.search.andCallFake(function(query) {
console.log("### Using location search mock");
if (typeof query != "undefined") {
console.log("### Setting search location: " + JSON.stringify(query));
this.location = JSON.stringify(query);
}
return this.location;
});
module(function($provide) {
$provide.value('$location', locationMock);
});
I didn't have to inject anything in the $controller. It just worked. Look at the logs:
LOG: '### Using location set'
LOG: '### Setting location: /test'
LOG: '### Using location search mock'
LOG: '### Setting search location: {"limit":"50","q":"ani","tags":[1,2],"category_id":5}'
If you want to check the arguments, spy on the method
// declare the cookieStoreMock globally
var cookieStoreMock;
beforeEach(function() {
cookieStoreMock = {};
cookieStoreMock.get = jasmine.createSpy("cookieStore.get() spy").andCallFake(function(key) {
switch (key) {
case 'cars':
return 0;
case 'bikes':
return 1;
case 'garage':
return {
cars: 0,
bikes: 1
};
}
});
module(function($provide) {
$provide.value('cookieStore', cookieStoreMock);
});
});
And then to test the argument do
expect(searchServiceMock.search).toHaveBeenCalledWith('cars');
Here is an example https://github.com/lucassus/angular-seed/blob/81d820d06e1d00d3bae34b456c0655baa79e51f2/test/unit/controllers/products/index_ctrl_spec.coffee#L3 it's coffeescript code with mocha + sinon.js but the idea is the same.
Basically with the following code snippet you could load a module and substitute its services:
beforeEach(module("myModule", function($provide) {
var stub = xxx; //... create a stub here
$provide.value("myService", stub);
}));
Later in the spec you could inject this stubbed service and do assertions:
it("does something magical", inject(function(myService) {
subject.foo();
expect(myService).toHaveBeenCalledWith("bar");
}));
More details and tips about mocking and testing you could find in this excellent blog post: http://www.yearofmoo.com/2013/09/advanced-testing-and-debugging-in-angularjs.html
Why mock cookieStore when you may use it directly without modification? The code below is a partial unit test for a controller which uses $cookieStore to put and get cookies. If your controller has a method known as "setACookie" that uses $cookieStore.put('cookieName', cookieValue) ... then the test should be able to read the value that was set.
describe('My controller', function() {
var $cookieStore;
describe('MySpecificController', function() {
beforeEach(inject(function(_$httpBackend_, $controller, _$cookieStore_) {
$cookieStore = _$cookieStore_;
// [...] unrelated to cookieStore
}));
it('should be able to reference cookies now', function () {
scope.setACookie();
expect($cookieStore.get('myCookieName')).toBe('setToSomething');
});
});

How can I simulate blur when testing directives in angularjs?

The problem
I am trying to test some directives (code for both below). One of them is an "email" (called "epost" in the code(norwegian)) directive. The solution to this should work for all of them, so I am keeping it to this one for now.
Technologies: Angularjs, Jasmine, Requirejs, (grunt & karma running in Chrome)
The directive validates email addresses in two ways; on upshift and on blur. I can test the upshift without problems as you can see in the test below, but I can't figure out how to simulate a blur so the bind('blur') in the directive runs.
What I have done
I have tried to catch the compiled element like this:
elem = angular.element(html);
element = $compile(elem)($scope);
And then in the test i tried several permutations to trigger the blur with a console log just inside the bind function in the directive. None of the below works. It does not trigger.
elem.trigger('blur');
element.trigger('blur');
elem.triggerHandler('blur');
element.triggerHandler('blur');
element.blur();
elem.blur();
I based the injection and setup on this: To test a custom validation angularjs directive
The email directive in angularjs wrapped in requirejs
define(function() {
var Directive = function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var pattern = /^[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
elem.bind('blur', function() {
scope.$apply(function () {
if (!elem.val() || pattern.test(elem.val())) {
ctrl.$setValidity('epost', true);
} else {
ctrl.$setValidity('epost', false);
}
});
});
ctrl.$parsers.unshift(function(viewValue) {
if (pattern.test(viewValue)) {
ctrl.$setValidity('epost', true);
return viewValue;
} else {
return undefined;
}
});
}
};
};
return Directive;
});
The test (using jasmine and requirejs)
define([
'Angular',
'AngularMocks',
], function () {
describe('Directives', function () {
var $scope;
var form;
beforeEach(module('common'));
beforeEach(function () {
var html = '<form name="form">';
html += '<input type="text" id="epost" name="epost" epost="" ng-model="model.epost"/>';
html += '</form>';
inject(function ($compile, $rootScope) {
$scope = $rootScope.$new();
$scope.model = {
epost: null
};
// Compile the element, run digest cycle
var elem = angular.element(html);
$compile(elem)($scope);
$scope.$digest();
form = $scope.form;
});
});
describe('(epost) Given an input field hooked up with the email directive', function () {
var validEmail = 'a#b.no';
var invalidEmail = 'asdf#asdf';
it('should bind data to model and be valid when email is valid on upshift', function () {
form.epost.$setViewValue(validEmail);
expect($scope.model.epost).toBe(validEmail);
expect(form.epost.$valid).toBe(true);
});
});
});
});
I have been able to figure out where I went wrong after some breakpoint debugging.
The "element" item I get out using the approach described in the top of the question is not actually the directive it self. It's an object which wraps the form and the directive.
Like this
{ 0: // The form
{ 0: // The directive (input element)
{
}
}
}
To actually simulate a blur on the directive it self, I did something like this
var directiveElement = $(element[0][0]);
directiveElement.blur();
After getting the element I wanted, and wrapping it in a jQuery object (may be optional), it worked like a charm. I then used the approach like in the test in the question with $setViewValue and checked the model value like this.
form.epost.$setViewValue('a#b.no');
directiveElement.blur();
expect($scope.model.epost).toBe('a#b.no');
expect($scope.form.epost.$valid).toBeTruthy();
Hope this could be of help to others trying to figure the directive testing out.
I too ran into a similar problem and it mystified me. My solution was to use JQuery to get the input and then use angular.element(input).triggerHandler('blur') to make it work. This is odd to me because I do not have to do this with the click event.
spyOn(controller, 'setRevenueIsInvalid');
var sugarRow = $(element).find('tr#ve_id_5')[0];
var amount = $(sugarRow).find('input.amount')[0];
angular.element(amount).triggerHandler('blur');
expect(controller.setRevenueIsInvalid).toHaveBeenCalled();