Simple Unit Testing with Web Atoms
Tags: typescript, showdev, javascript, testing
Published: 2020-02-18 06:59:01

# Test Class and Method

I know many would be unhappy seeing classes, but classes are only used to decorate and organize code, you can always do more functional things with Web Atoms Unit Tests.

```typescript

/** Category decorator is optional, it just lets you specify name of test instead of name of class that extends TestItem */
@Category("Test Case 1")
export default class TestCase1 extends TestItem {

    @Test
    public parseTest() {
        Assert.equals(2, parseInt("2", 10));
    }

    /** You can specify name of test */
    @Test("Parse Float Test")
    public parseFloat() {
        Assert.equals(2.5, parseFloat("2.5"));
    }

}

```
### How to run the test?
You can execute `@web-atoms/unit-test/index.js` script against folder of all scripts. You can also give path to single test file as well.
```node
d:\git\test> node ./node_modules/@web-atoms/unit-test/index.js ./dist

Test Case 1 > parseTest success.
Test Case 1 > Parse Float Test success.
```

# Async Tests

Your test method can also return Promise and it unit test engine will await for the result.

```typescript

@Category("Async Test 1")
export default class TestCase1 extends TestItem {

    @Test
    public async asyncTestOne() {
        const p = method that returns Promise ....
        await p;
    }

}

```

# Global Separation

Each unit test executes in a separate `Global` context in Node, this isolates every unit test method. Even same test methods in same class do not share same context.


```typescript

declare var global: any;

export default class TestCase1 extends TestItem {

    @Test
    public async parseTest() {
        global.a = 1;
        await Atom.delay(100);
        Assert.equal(1, global.a);
    }

    @Test
    public async parseTest2() {
        global.a = 2;
        await Atom.delay(50);
        Assert.equal(2, global.a);
    }

}
```

Both tests will be successful.

# How to test ViewModel?

Since AtomViewModel requires dependency injection, we have created a class `AtomTest` which sets up dependency injection with Mock Service Container.

This class contains `app` and it has a method `waitForPendingCalls` which allows you to wait for all pending initializations.

```typescript
class TestVM extends AtomViewModel {

   public movies: IMovie[];

   public search: string = null;

   @Inject private movieService: MovieService;

   /** This will be executed when vm is initialized and it will
    * be executed when search will be updated 
    */
   @Load({ init: true, watch: true })
   public async loadItems(ct: CancelToken) {
      const s = this.search;
      this.movies = await this.movieService.loadMovies(s, ct);
   }

}

export default class TestVMCase extends AtomTest {

    @Test
    public async vmTest() {
        // this waits till loading of vm is complete
        const vm = await this.createViewModel(TestVM);
        Assert.equals(5, vm.movies.length);

        // lets update search
        vm.search = "a";
 
        // lets wait for few milliseconds
        // and we assume that mock will populate
        // results...
        await Atom.delay(100);
        Assert.equals(1, vm.movies.length);
    }

}

```

# Mocks

Every dependency can specify `mock` as shown below.

```typescript
@DISingleton({ mock: "./mocks/MockMovieService" })
export default class MovieService extends BaseService {

    @Get("/api/movies")
    public async loadMovies(
        @Query("search") search: string,
        ct: CancelToken) {
        return null;
    }
}
```
`./Mocks/MockMovieService`
```typescript
export default class MockMovieService {

    public async loadMovies(
        search: string,
        ct: CancelToken) {
        // create artificial delay...
        await Atom.delay(1);
        if (ct.cancelled) throw new Error("cancelled");
        return [
           ... movies 
        ];
    }
}
```