Check out our Angular Book Series.

How do I copy files with Maven?

Last week, I wrote about running a Gulp Script from Maven. The Gulp Script was located in a directory outside of the Java project.

This week I'll show you how to copy the file from the external directory into the Java project.

We're going to use the maven-resources-plugin to make this work.

First, set it up as a plugin in the Maven POM file:


<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>

This adds the artifactId--AKA the plugin name--and the version number. For each set of files we want to copy, we'll need to add an execution block, so start there:


<executions>
<execution>
<id>copy-resources-A5</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>

This specifies the id of the copy, which is just a unique name for the process. It is done on the validation phase. And the goal is to copy-resources.

Before we end the execution block, we need to add the configuration. this will tell us what we are copying and where we are copying it too.


<configuration>
<outputDirectory>${basedir}/src/main/webapp/A5</outputDirectory>
<resources>
<resource>
<directory>C:/Projects/lw/A5/chapter7/Angular5TypeScript/build</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>

The outputDirectory specifies the location of the copied files. I made them relative tot he $(basedir), which is the root of the Java project. In the Angular 5 files in the A5 directory of the Java Project's webapp.

The source directory is listed under resources, resource.

Finally close off the open tags, which is the execution tag, the executions tag, and the plugin tag:


</execution>
</executions>
</plugin>

I'm using this approach to copy the results of an external Gulp script into the final WAR, so that the files will be surfable via the Tomcat instance.

It has been working great for me.

How do I run a Gulp Script in a different directory from Maven?

If you want to run a Gulp Script from Maven, you use the frontend plugin, right? Unfortunately, that seems to assume that the Gulp script is part of the Java project.

I'm creating a Java Backend to the LearnWith series and am sharing UI code between multiple backend technologies. As such, my directory structure is different and the UI code is not in the web root. I want to run a gulp script in a different directory, and then copy the results into the project's webapp directory. How do you do it?

I was able to do it with the exec Maven Plugin.

First, set up the plugin in the plugins portion of Maven POM:


<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>

The groupId, arftifactId, and version are specified. For each script you want to run you'll need an execution statement:


<executions>
<execution>
<id>Build Angular 5</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>gulp</executable>
<arguments>
<argument>build</argument>
</arguments>
<workingDirectory>C:\Projects\lw\A5\chapter7\Angular5TypeScript</workingDirectory>
</configuration>
</execution>
</executions>

The id is a unique name for the build script. The phase is validate, because it is. The goal should be set to exec. The configuration is where the real magic happens. We tell it to execute the gulp command with the argument 'build', so this will run:


gulp build

We want to make sure that the script runs in the proper directory and that is specified with the workingDirectory tag. Presumably this directory will be outside the current directory structure.

Finally, close the plugin tag:


</plugin>

That's all you need to do to run an external Gulp Script from a Maven project. This assumes, of course, you have Gulp installed. Next week I'm going to write about copying the processed files into the Java project.

How do you get the ID of the inserted row with Java and SQL Server?

I've been playing around with the Learn With series and building the backend services in Java. I was going right along until I got to the chapter to create and update data. The query is something like this:


String SQL = "insert into tasks(taskCategoryID, userID, description, completed, dateCreated) values(?,?, ?, 0, ? )
SELECT SCOPE_IDENTITY() as taskID";

This is a super common approach to creating a new item in SQL Server, and then immediately selecting the newly created ID with SCOPE_IDENTITY(). I'd expect it to work easily.

The rest of the code:


PreparedStatement pstmt = connection.prepareStatement(SQL);

// set parameters
pstmt.setInt(1, taskCategoryID);
pstmt.setInt(2, userID);
pstmt.setString(3, description);
LocalDate dateCreated = LocalDate.now();
pstmt.setObject(4, dateCreated);
ResultSet rs = pstmt.executeQuery();

The last line will throw an error claiming that the query returned no results sets and blow up. I had been swimming along smoothly until I ran into that issue. It turns out that, for updates and inserts, that is the wrong approach. Instead of using executeQuery(), I need to use executeUpdate(), like this:


pstmt.executeUpdate();

After that, I can get the second result set using this:


ResultSet rs = pstmt.getGeneratedKeys();

Then things are good to go.

You'll probably find the LearnWith series supporting Java next month.

How do I copy Multiple Directories with Maven?

I've been playing around with Java since a lot of my clients use it as their service layer to power their Angular applications. As such I'm digging into Maven a common build tool for Java applications.

Java development is more complicated than something like PHP or ColdFusion. With PHP or ColdFusion I can just write a file, throw it in a web directory and load it. Java requires a compilation process, which complicates things a bit. Since I'm building a service layer as part of the Learn With applications, I wanted to copy specific files into my final WAR file. I wanted to copy the front end from the books into the final WAR file, without having to replicate all the UI code in the Java project.

The Maven ">Resource plugin lets me copy files from outside the Java project into the Java project before building the project. Something like this works:


<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>copy-resources</id>
<!-- here the phase you need -->
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/src/main/webapp/your-dir-here</outputDirectory>
<resources>
<resource>
<directory>C:/non-packaged-resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>

This works great to copy non-project files from the resource directory:


<resource>
<directory>C:/non-packaged-resources</directory>
<filtering>true</filtering>
</resource>

This will be external to the project. Into the outputDirectory


<outputDirectory>${basedir}/src/main/webapp/your-dir-here</outputDirectory>

Which is part of the project. Once the files are in the web-app directory they will be compiled into the final WAR file and accessible when that WAR is deployed to a Java server, such as Tomcat.

However, I'm dealing with multiple UI technologies and wanted to copy over all the files. How do I do it? The answer is to put multiple execution blocks inside the tag:


<executions>
<execution>
<id>copy-resources-AJS</id>
<!-- here the phase you need -->
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/src/main/webapp/AJS</outputDirectory>
<resources>
<resource>
<directory>C:/Projects/lw/AJS/chapter1/angularApp</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-resources-A4</id>
<!-- here the phase you need -->
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/src/main/webapp/A4</outputDirectory>
<resources>
<resource>
<directory>C:/Projects/lw/A4/chapter1/Angular4TypeScript/build</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-resources-A5</id>
<!-- here the phase you need -->
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/src/main/webapp/A5</outputDirectory>
<resources>
<resource>
<directory>C:/Projects/lw/A5/chapter1/Angular5TypeScript/build</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>

Make sure that each execution block has a unique ID, or else Maven will get confused. This copies my Angular 5 source files to:


${basedir}/src/main/webapp/A5

And they can be surfable using "http://localhost:port/A5/"

The Angular 4 source files to:


${basedir}/src/main/webapp/A4

And they can be surfable using "http://localhost:port/A4/"

And the AngularJS files will be put to:


${basedir}/src/main/webapp/AJS

And they can be surfable using "http://localhost:port/AJS/".

That is how you can copy multiple external directories into your Java project using Maven.

Why won't Jersey work on JDK 9?

I've been experimenting with writing REST Services in Java. A ton of clients have used Java to power their backend's over the years, but I never created a REST Service environment from scratch myself. I decided to tackle it during the holiday time and educate myself. I try to focus on front end programming technologies, but knowing about the backend is beneficial.

I decided to use Jersey to build out REST Services in Java and the road has been rocky.

I have a Maven project setup to use Tomcat 8.5 and Java 9.01. Unfortunately, I kept seeing a lot of errors like this in the Java console:


java.lang.IllegalArgumentException: Errors were discovered while reifying SystemDescriptor(
    implementation=org.glassfish.jersey.jaxb.internal.XmlRootObjectJaxbProvider$General
    contracts={javax.ws.rs.ext.MessageBodyReader}
    scope=javax.inject.Singleton
    qualifiers={}
    descriptorType=CLASS
    descriptorVisibility=NORMAL
    metadata=
    rank=0
    loader=null
    proxiable=null
    proxyForSameScope=null
    analysisName=null
    id=130
    locatorId=0
    identityHashCode=1215770148
    reified=false)
    at org.jvnet.hk2.internal.SystemDescriptor.reify(SystemDescriptor.java:705)
    at org.jvnet.hk2.internal.ServiceLocatorImpl.reifyDescriptor

etc.. etc..

or this:


java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext
    at org.glassfish.hk2.utilities.cache.LRUHybridCache.compute(LRUHybridCache.java:315)
    at org.glassfish.hk2.utilities.reflection.internal.ClassReflectionHelperImpl.getAllMethods(ClassReflectionHelperImpl.java:108)
    at org.glassfish.hk2.utilities.reflection.internal.ClassReflectionHelperImpl.getPreDestroyMethod(ClassReflectionHelperImpl.java:209)
    at org.glassfish.hk2.utilities.reflection.internal.ClassReflectionHelperImpl.access$400(ClassReflectionHelperImpl.java:56)
    at org.glassfish.hk2.utilities.reflection.internal.ClassReflectionHelperImpl$2.compute(ClassReflectionHelperImpl.java:74)
    at org.glassfish.hk2.utilities.reflection.internal.ClassReflectionHelperImpl$2.compute(ClassReflectionHelperImpl.java:70)
    at org.glassfish.hk2.utilities.cache.LRUHybridCache$OriginThreadAwareFuture$1.call(LRUHybridCache.java:115)
    at org.glassfish.hk2.utilities.cache.LRUHybridCache$OriginThreadAwareFuture$1.call(LRUHybridCache.java:111)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at org.glassfish.hk2.utilities.cache.LRUHybridCache$OriginThreadAwareFuture.run(LRUHybridCache.java:173)
    at org.glassfish.hk2.utilities.cache.LRUHybridCache.compute(LRUHybridCache.java:292)
    at
etc..etc..

They went on and on. Basically, some libraries that are required by Jersey are no longer included as part of the Java 9 SDK. They were there in Java 8, but were removed. These errors exist because the libraries cannot be found.

The trick to remove these errors is to load these libraries using Maven. Find the pom.xml that builds your project and find the dependencies section. It will probably look something like this:


<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<!-- use the following artifactId if you don't need servlet 2.x compatibility -->
<!-- artifactId>jersey-container-servlet</artifactId -->
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<!-- uncomment this to get JSON support
-->

<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
</dependency>
</dependencies>

We need to add back in the missing dependencies. There are two libraries that were required:


<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1</version>
</dependency>

Relaunch your Tomcat instance and you are good to go. No errors and your Jersey services should work without issue.

How do I create a REST service in PHP?

I've been working to update the LearnWith series. The series builds UI applications--right now with Angular--and multiple backends with NodeJS, or ColdFusion. I'm creating a new book that creates services in PHP.

How do you create a REST service in PHP?

I did it this way. First, add a header:


header("Content-Type: application/json; charset=UTF-8");

The Content-Type header says the results will be in JSON.

Then, get the request type:


$method = $_SERVER['REQUEST_METHOD'];

The $method variable will be GET, or POST, or PUT, or whatever REST protocol you're using.

Then, use a switch statement to determine the type of request:

switch ($method) {
case 'GET':
// access URL Variables
// $_GET["myvar"])
// data retrieval here;
echo(json_encode($result));
break;
case 'PUT':
// retrieve PUT body data
$data = json_decode(file_get_contents('php://input'));
// $data->
myvar
// Update data Here
echo(json_encode($result) );
break;
case 'POST':
// retrieve POST Body data
$data = json_decode(file_get_contents('php://input'));
// $data->myvar
// Create some data Here
echo(json_encode($result) );
break;
default:
echo("Unknown Request Type");
break;
}

That is pretty much it. For get requests, you can access URL variables directly, like this:


$_GET["myvar"])

For PUT or POST requests, you'll most likely be sending a JSON object as the body of the request and you can access it like this:


$data = json_decode(file_get_contents('php://input'));

I hope this helps someone. Be sure to check out my LearnWith series, where you'll be able to find information about integrating Angular and AngularJS with PHP very soon.

How do I Fix Uncaught (in promise): Error: StaticInjectorError[] in Angular 5?

I've updated my Learn With Programming Books to Angular 5. Angular changed their versioning structure, so Angular 5 is more like Angular 2.2 than a complete update.

Unfortunately, the upgrade was not seamless and this error drove me insane. Everything I thought I'd fix it, it would crop back up:


ERROR Error: Uncaught (in promise): Error: StaticInjectorError[UserModel]:
StaticInjectorError[UserModel]:
NullInjectorError: No provider for UserModel!
Error: NullInjectorError: No provider for UserModel!
at _NullInjector.get (injector.js:31) [angular]
etc.. etc..

The error looked like this:

The underlying cause of this error is that Angular 5 changed how they create providers moving from a reflect based injector to a static injector. This removes the need for the reflect polyfill that is used in Angular applications. Without digging deep into Angular code I can't explain better, all I knew was that I had the error and needed a resolution.

If you search, you'll find a lot of write ups on this, but it was trial and error that solved the issue for me. Here are two things to look at that helped me solve the issue.

Check Your TypeScript Version

Angular 5 requires TypeScript 2.3 or later, so make sure that your build scripts have the proper version installed. I'm using TypeScript 2.4, and your package.json should look something like this:


"typescript": "^2.4.0",

If you're using the Angular CLI this shouldn't be an issue, as it should already have the proper versions set up.

Check Case Sensitivity of Classes and Imports

This is the one that drove me nuts. About half of my development standards come from 'legacy' applications, and the rule of thumb used to be to name class files in proper case, so UserModel.ts instead of usermodel.ts. However, it appears--and I'm not sure why--that Angular has a problem with the mixed case of the class imports. If I was importing two classes from the same directory and setting them up as providers in the main application, I would get this error.

This would cause errors:


import {UserModel} from "../model/UserModel";
import {TaskModel} from "../model/TaskModel";


@NgModule({
// other stuff here
providers : [ UserModel, TaskModel]
})

This would not:


import {UserModel} from "../model/usermodel";
import {TaskModel} from "../model/taskmodel";


@NgModule({
// other stuff here
providers : [ UserModel, TaskModel]
})

Even if I changed nothing with the actual import files. By putting all the class imports in lower case, I was able to avoid the issue.

That was until I set up unit tests using karma-typescript. Karma TypeScript spit up on the imports with incorrect case. I ended up putting the file names in all lower case so they would be fine with my Angular Build Scripts and with the Karma TypeScript tests.

Final Thoughts

I thought the upgrade, and book update, would be a couple of days, but took me a full week. So, go check out my Angular 5 books, or at least get on my mailing list using the form below.

Keep up to date by reading DotComIt's Monthly Technical Newsletter

Enable Double Opt In for MailChimp APIs

I was using some very old code to power the "Join my mailing list" boxes on my various sites. It stopped working perfectly a while back and I've been rewriting the code to use the MailChimp 3.0 APIs instead of the MailChimp 1.3 API.

Setting it up was a lot easier than I expected. The first thing I noticed after rolling it out, however, was that users were being subscribed immediately to the mailing list without using the double opt in. Obviously this is a nightmare, despite the new MailChimp defaults. You do not ever want use single opt in.

I was using this API to add new members to a list. I pulled directly from a sample in their docs:


{
"email_address": "urist.mcvankab@freddiesjokes.com",
"status": "subscribed",
"merge_fields": {
"FNAME": "Urist",
"LNAME": "McVankab"
}
}

The reason double opt in was being bypassed was because of the status of the user. I put the status as subscribed, which is a way to tell mailchimp they already double opted in. To fix my problem, I needed to change to that pending, something like this:


{
"email_address": "urist.mcvankab@freddiesjokes.com",
"status": "pending",
"merge_fields": {
"FNAME": "Urist",
"LNAME": "McVankab"
}
}

The API assumes that subscribed means they are in, but pending means they still need to approve the sign up. More details on the user statuses are on the same page with information about managing subscribers, but sadly not mentioned on the API Doc page.

I'm surprised how easy it was to set up the API Integration, so Kudos to MailChimp for that. I wish the API Docs were more detailed on the meaning and values of certain fields.

General Life Lessons

I've recently started to watch reddit on a regular basis. Generally the conversation and information I Get there is a lot more interesting than Facebook. In the Computer Science Career Questions subreddit. The subreddit seems to be made up of primarily students or people on their first two years of the job. Someone asked me for some valuable life lessons, and my response has been getting way more upvotes than I'm used to. I thought I'd make an interesting non-technical blog post.

Here are some of the advice I'd give to people just starting out their careers. In no particular order:

  • Don't Quit: Keep your current job until you have a new one lined up. There is some psychology involved. When people hear you are happily employed they experience the thrill of the chase. When they don't have a job they think there may be something wrong with you. Ashton Kutcher has some great words on this sentiment.
  • Know Your Priorities: I spent the past 18 years as a consultant running my own biz. Most people view me as a successful businessman, but I've had a lot of missteps. I would probably be a lot richer going the 'traditional' route and switched jobs every 3-5 years. I have always valued flexibility over money. What are your values and how does that apply to your career choices?
  • Don't be Afraid to Fail: I've been involved in ~15 different ventures of varying success over the past 30 years. Hugely satisfying even if not always profitable. Go for it!
  • Control your Spending: Research the FIRE movement. FIRE stands for Financial Independence, Retire Early. The gist is keep your expenses low, invest what is left over and soon the investment return will be more than your expenses, eliminating the need to have a job. I'm not FIRE yet, but keeping expenses low is part of what kept me in business during various failures. Corollary: Max out your tax deductible retirement contributions. Mr Money Moustache has become an unofficial spokesperson for the for the FIRE movement.
  • Learn how to Communicate: Take a writing course or public speaking course. This is probably more important to your future job prospects than anything technology related. Taking a Dale Carnegie course is recommended.
  • Keep in Touch: Reach out to friends and colleagues at least once a year to say "Howdy, let's do lunch." You never know where your next opportunity will come from, and even if nothing comes of it it's good to keep connections with people.
  • Technology works on a Cycle: Everything old becomes new again. Lotus Notes was a 'NoSQL' database in the 80s/90s that worked on similar client-server principles that the web works on today. The VAX machine my college uses had dumb terminals with a server doing the heavy work--not unlike a Chromebook using Google Services. Programming languages are similar. It is often hilarious to me see "all ya kids" trumpeting these brand new concepts from 20 or 30 years ago. Corollary: Businesses also work on a cycle and decentralize their infrastructure to increase efficiency and remove bottlenecks and centralize to increase company wide cohesiveness.
  • Be Niche: I find there are more opportunities being a specialist than a generalist. But, don't be afraid to change that specialty, though. A decade ago I was one of the most prominent Flex/Flash Developers in the world, but that technology is less important today. Two decades ago I was a ColdFusion expert, a tech that is also less relevant today. Both of these skills still get me client work, though.
  • Own You: A lot of the things you'll build for employers will be owned by them. Try to own as much as you as you can. Always be aware of the rights you're giving up. I recently turned down a book deal with a major tech publisher because they would not explicitly say that my self published writings were non-competitive.
  • Read Contracts: Read something before you sign it. Ask for a copy for your records.

Hopefully you find this interesting. I'll be back next week with something more technical. ;)

Testing a Bootstrap Popup with Angular - Part 2

This is the second half of my article on testing a Bootstrap popup. Before reading this, you should read my post about creating a popup with Angular and Bootstrap, and the one about testing the popup component. This post will focus on testing the code that creates the popup.

Review Code

In my test sample, the code to create the popup is in the app.component.ts file. This is it:


onModalRequest():void {
const modalRef = this.modalService.open(PopupComponent );

modalRef.result.then((result) =>
{
console.log(result);
console.log('closed');
}).catch( (result) => {
console.log(result);
console.log('cancelling');
});
}

This is triggered by button click in the view. Create an instance of the modal using the modalService, which is an instance of the NgbModal. It saves that instance in another variable, modalRef. The modalRef.result is a promise, and we can use that to run code whenever the modal is closed or dismissed. The promise then() method represents a successful closure of the modal. The promise catch() method represents a dismissal. Since this is a test app, the close and dismiss methods don't do anything other than log items out to the console, but a real app may save data or update a view.

We're going to write a few tests against this code. The first will just verify that the modal opened. Then we'll open and close the modal, verifying that the result function was called. Then we'll open and dismiss the modal, verifying that the catch function was called.

Write the Tests

I put my tests in a file named app.component.test.ts. As always, we'll start with the imports. These are the Angular testing imports and the ng-bootstrap imports:


import {async, TestBed,ComponentFixture} from '@angular/core/testing';
import {NgbModal, NgbModalRef } from "@ng-bootstrap/ng-bootstrap";

Now, load our custom components:


import {AppComponent} from "../../../../../src/com/dotComIt/learnWith/main/app.component";
import {PopupComponent} from "../../../../../src/com/dotComIt/learnWith/views/popup/popup.component";

We import the AppComponent, which contains the code we'll be testing and the PopupComponent which contains the code we'll be testing. Now, create a describe function to create the set of unit tests:


describe('AppComponent', function () {
});

Next, create a bunch of variables inside the describe:


let fixture: ComponentFixture<AppComponent>;
let appComponent: AppComponent;
let modalService: NgbModal;
let modalRef: NgbModalRef;

The ComponentFixture is used to create an instance of a component so you can access all properties, methods, and DOM elements of its HTML template. The appComponent is an instance of the component's class. The modalService and modalRef relate to the popup window that will be created.

Here is a beforeEach():


beforeEach(async(() =>
{
TestBed.compileComponents().then(() => {
modalService = TestBed.get(NgbModal);
modalRef = modalService.open(PopupComponent);
fixture = TestBed.createComponent(AppComponent);
appComponent = fixture.componentInstance;
spyOn(modalService, "open").and.returnValue(modalRef);
spyOn(console,'log').and.callThrough();
});
}));

It compiles the components on the TestBed. Remember the TestBed was configured in a base.test.ts file, which our scripts know to compile first. After the components are compiled, we get the modalService and create a modalRef by opening the component. The fixture instance of the AppComponent is stored as is its componentInstance.

Finally the beforeEach() creates two spyOn() methods. It looks at the open method of the modalService, and tells it to return the modalRef as a specific value. Then it spys on the console.log() method. Normally I would try to avoid spying on this, but since no real functionality exists inside our app this is the best way to determine if the close or dismiss methods will run on the modal popup.

Let's start off with an easy test, to see that the modal opened:


it('Modal Opened', function () {
appComponent.onModalRequest();
expect(modalService.open).toHaveBeenCalled();
});

This is pretty self-explanatory. It calls the appComponent's onModalRequest() creates the modal, and checks to see that the open method had been called, insinuating that the modal had been created.

Let's create the modal, then close it:


it('Modal Opened, then Closed', (done : DoneFn) =>
{
appComponent.onModalRequest();
fixture.detectChanges();
fixture.whenStable().then(() => {
modalRef.close();
fixture.whenStable().then(() => {
expect(console.log).toHaveBeenCalledWith('closed')
done();
});
});
});

First thing you notice is that a done() function is passed into the test function. This is a function that tells Jasmine to run the code asynchronously. This is a special Jasmine construct, and is different than the async or fakeAsync test beds which are Angular constructs. The Angular constructs have all sorts of issues if you use any sort of timer under the hood, and gave me lots of problems when I was writing the test code for the learn with app. This approach avoids those issues.

Next, the onModalRequest() method is called on the appComponent. Then we access the fixture to detectChanges(). This tells the component to process a digest cycle. It will redraw whatever needs redrawing. In our case, it waits for the modal to actually be created and opened. Then we wait until the fixture is stable using the whenStable() function. This means the modal was created and it is all good to continue. The whenStable() method returns a promise, and we execute the success function on it. Inside the success function we call a close() method on the modalRef value. We do not need to call the detectChanges() again, but we do need to wait until code is stable before running our assertion. The assertion excepts that console.log() will have been called with the value "closed". This is the hard coded result value inside the main app component code. Finally it calls the done() function to tell Jasmine that we are done running async code. This completes the test

To test the dismiss function, we use a similar approach, but I needed an extra level of waiting until the component fixture was stable before I could check for the dismiss function to have been called:


it('Modal Opened, then dismissed', (done : DoneFn) =>
{
appComponent.onModalRequest();
fixture.detectChanges();
fixture.whenStable().then(() => {
modalRef.dismiss();
fixture.whenStable().then(() => {
fixture.whenStable().then(() => {
expect(console.log).toHaveBeenCalledWith('cancelling')
done();
});
});
});
});

The code opens the modal with appcomponent.onModalRequest(). Then it calls fixture.detectChanges() to watch for the DOM changes. Then it uses whenStable() to make sure the modal opened. When it is open, it is dismissed. Then it waits until it's stable twice before running the assertion. The assertion checks that the console.log() method was called with the 'cancelling' string. Then it calls the done() function, finishing off this method.

Run it:


Gulp test

And you'll see results like this:

Final Thoughts

When researching how to test a popup with Bootstrap and Angular 4 I found a lot of conflicting information. But, I hobbled through it and found this approach which seemed to work well for me. I wrote a full 70 pages on Unit Testing in the bonus book to my Angular 4 series. Get it now!

Keep up to date by reading DotComIt's Monthly Technical Newsletter

More Entries

All Content Copyright 2005, 2006, 2007, 2008, 2009 Jeffry Houser. May not be reused without permission
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002.