I'm using two libraries to make streams: the native and getX. I created two groups for tests: the native and getX with same contract from an abstract class (AnyPresenter). One of them is Stream<bool> get isLoadingStream which for the native is _state.isLoading and for getx is isLoading.
The Streams only change when run the method below:
Native:
Getx:
I'm using the same test for both, but it is not running correctly in the native test.
Expect result:
[true, false]
Native error:
This is the sample code:
import 'dart:async';
import 'package:get/get.dart';
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
class AnyState {
bool isLoading = false;
}
abstract class AnyPresenter {
Stream<bool> get isLoadingStream;
void doSomething();
}
class StreamAnyPresenter implements AnyPresenter {
final AnyLibrary anyLibrary;
var _controller = StreamController<AnyState>.broadcast();
var _anyState = AnyState();
StreamAnyPresenter(this.anyLibrary);
Stream<bool> get isLoadingStream =>
_controller.stream.map((anyState) => anyState.isLoading).distinct();
Future<void> doSomething() async {
_anyState.isLoading = true;
_controller.add(_anyState);
_anyState.isLoading = false;
_controller.add(_anyState);
}
}
class GetXPresenter implements AnyPresenter {
final AnyLibrary anyLibrary;
var isLoading = false.obs;
GetXPresenter(this.anyLibrary);
Stream<bool> get isLoadingStream => isLoading.stream.distinct();
Future<void> doSomething() async {
isLoading.value = true;
isLoading.value = false;
}
}
abstract class AnyLibrary {
Future<void> someMethod();
}
class AnyLibrarySpy extends Mock implements AnyLibrary {}
void main() {
AnyLibrarySpy anyLibrary;
setUp(() {
anyLibrary = AnyLibrarySpy();
});
group('Using native Stream', () {
StreamAnyPresenter sut;
setUp(() {
sut = StreamAnyPresenter(anyLibrary);
});
test('Should emit events on Success', () async {
expectLater(sut.isLoadingStream, emitsInOrder([true, false]));
await sut.doSomething();
});
});
group('Using getX', () {
GetXPresenter sut;
setUp(() {
sut = GetXPresenter(anyLibrary);
});
test('Should emit events on Success', () async {
expectLater(sut.isLoadingStream, emitsInOrder([true, false]));
await sut.doSomething();
});
});
}
Sorry for any mistakes. English is not my native language
Related
I'm hoping to get some insight into an issue I'm having.
I have a mixin that produces a guard. The resulting guard uses a service that is injected. Here's the code for the mixin:
import {
CanActivate,
ExecutionContext,
Injectable,
mixin,
} from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AccountService } from 'src/modules/account/account.service';
export const ForAccountGuard = (
paramName: string,
{ required = false }: { required?: boolean } = {}
) => {
#Injectable()
class _ForAccountGuard implements CanActivate {
constructor(private readonly accountService: AccountService) {}
async canActivate(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const accountId = ctx.getArgs()[paramName];
const currentUser = ctx.getContext().user;
if (required && !accountId) {
return false;
}
if (accountId) {
const account = await this.accountService.findUserAccount(
accountId,
currentUser.id
);
return !!account;
}
return true;
}
}
return mixin(_ForAccountGuard);
};
In my tests for a resolver that uses this mixin as a guard I'm doing the following:
#Query(() => [PropertyEntity])
#UseGuards(ForAccountGuard('accountId'))
async allProperties(#Args() { accountId }: AllPropertiesArgs) {
// <implementation removed>
}
So, the issue I'm running into is that I get the following error when running tests:
Cannot find module 'src/modules/account/account.service' from 'modules/common/guards/for-account.guard.ts'
Require stack:
modules/common/guards/for-account.guard.ts
modules/property/property.resolver.spec.ts
It looks like the injected AccountService isn't being resolved.
I'm not exactly sure how to tell Nest's testing module to override a guard that is a mixin. I've been trying it like this, but it doesn't seem to be working:
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PropertyResolver,
...
],
})
.overrideGuard(ForAccountGuard)
.useValue(createMock<typeof ForAccountGuard>())
.compile();
);
});
So, how am I supposed to mock out a guard that is a mixin?
Okay, after tinkering a bit, I figured out a solution.
Abandoning the overrideGuard method and just going for a straight-up jest.mock of the entire mixin seems to do the trick.
So, I created a mock:
import { CanActivate, Injectable, mixin } from '#nestjs/common';
export const mockForAccountGuard = () => {
#Injectable()
class _ForAccountGuardMock implements CanActivate {
canActivate() {
return true;
}
}
return mixin(_ForAccountGuardMock);
};
And then I used jest.mock to mock it out:
// in my test file
import { mockForAccountGuard } from '../common/guards/__mocks__/for-account.guard';
import { ForAccountGuard } from '../common/guards/for-account.guard';
jest.mock('../common/guards/for-account.guard', () => ({
ForAccountGuard: mockForAccountGuard,
}));
...
describe('PropertyResolver', () => {
...
beforeEach(() => {
...
const module: TestingModule = await Test.createTestingModule({
...
}).compile() // note, not using overrideGuards here
});
})
And that seems to do the trick!
I'm using NodeJS and a MongoDB. I have this simple function for returning a generic property of a document ...
import mongoose, { Document, Schema } from "mongoose";
export interface IMyObject extends Document {
...
}
...
export async function getProperty(
req: CustomRequest<MyDto>,
res: Response,
next: NextFunction
): Promise<void> {
const {
params: { propertyName, code },
} = req;
try {
const my_obj = await MyObject.findOne({ code });
const propertyValue = my_obj ? my_obj.get(propertyName) : null;
if (propertyValue) {
res.status(200).json(propertyValue);
...
I'm struggling to figure out how to test this function. In particular, how do I mock an instance of my object that's compatible with the "get" method? I tried this
it("Should return the proper result", async () => {
const myObject = {
name: "jon",
};
MyObject.findOne = jest.fn().mockResolvedValue(myObject.name);
const resp = await superTestApp.get(
"/getProperty/name/7777"
);
expect(resp.status).toBe(StatusCodes.OK);
expect(resp.body).toEqual("happy");
but this fails with
TypeError: my_object.get is not a function
You would need to spy your object and its methods. Something like:
import MyObject from '..';
const mockedData = {
get: (v) => v
};
let objectSpy;
// spy the method and set the mocked data before all tests execution
beforeAll(() => {
objectSpy = jest.spyOn(MyObject, 'findOne');
objectSpy.mockReturnValue(mockedData);
});
// clear the mock the method after all tests execution
afterAll(() => {
objectSpy.mockClear();
});
// call your method, should be returning same content as `mockedData` const
test('init', () => {
const response = MyObject.findOne();
expect(response.get('whatever')).toEqual(mockedData.get('whatever'));
});
I have a basic serverless application below, I want to test using Jest if getUserById method is called. I am also using inversifyjs. Now when I run my test I am getting an error TypeError: Reflect.hasOwnMetadata is not a function. Another thing how can I mock a response here?
handler.spec.ts
import { IUsersHandler } from './../src/IUsersHandler';
import { UsersHandler } from './../src/UsersHandler';
import { APIGatewayProxyResult } from 'aws-lambda';
let handler: IUsersHandler;
let mockResponse: APIGatewayProxyResult;
describe("UsersHandler", () => {
beforeEach(() => {
handler = new UsersHandler();
});
it("should call getUserById method", () => {
const spy = jest.spyOn(handler, 'getUserById').mockImplementation(async () => mockResponse);
expect(spy).toBeCalledTimes(1);
});
});
UsersHandler Class
import { IUsersHandler } from './IUsersHandler';
import { injectable } from "inversify";
import { APIGatewayProxyHandler, APIGatewayProxyResult, APIGatewayProxyEvent } from "aws-lambda";
#injectable()
export class UsersHandler implements IUsersHandler {
constructor() { }
public getUserById: APIGatewayProxyHandler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
try {
return {
statusCode: 200,
body: JSON.stringify(event)
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify(err)
};
}
};
}
User Interface
import { APIGatewayProxyHandler } from 'aws-lambda';
export interface IUsersHandler {
getUserById: APIGatewayProxyHandler;
}
export const TUsersHandler = Symbol.for('IUsersHandler');
Handler.ts
import { IUsersHandler, TUsersHandler } from './src/IUsersHandler';
import { container } from "./src/inversify.config";
import 'source-map-support/register';
export const getUserById = async function (event, context, callback) {
const handler: IUsersHandler = container.get<IUsersHandler>(TUsersHandler);
return handler.getUserById(event, context, callback);
};
Final handler.spec.ts
import "reflect-metadata";
import { IUsersHandler } from "./../src/IUsersHandler";
import { UsersHandler } from "./../src/UsersHandler";
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
let handler: IUsersHandler;
let mockEvent: APIGatewayProxyEvent;
let mockResponse: APIGatewayProxyResult;
describe("UsersHandler", () => {
beforeEach(() => {
mockResponse = {
statusCode: 200,
body: "This is a test",
};
handler = new UsersHandler();
});
it("should call getUserById method", async () => {
const spy = jest
.spyOn(handler, "getUserById")
.mockImplementation(async () => mockResponse);
const response: any = await handler.getUserById(mockEvent, null, null);
expect(spy).toBeCalledTimes(1);
expect(response.body).toBe("This is a test");
expect(response.statusCode).toBe(200);
});
});
Right now I am writing an unit test for this kind of situation like below:
public div: HTMLDivElement;
public currentEvent: EventType;
public listenToRender() {
this.adsService.render.filter((event: EventType) => {
return this.div.id === event.slot.getSlotElementId();
}).subscribe((event: EventType) => {
let custom_event = new CustomEvent('render', {
detail: event
});
this.currentEvent= event;
});
}
During the unit test, I mock the render with subject, but I don't know how I can make it pass the filter
return this.div.id === event.slot.getSlotElementId();
and go to the subscribe function.
class MockAdsService {
render = new Subject();
}
class MockEventType {
name: 'test_event';
slot: {
getSlotElementId = function() {return 'test_id'}
};
}
describe('test', () => {
let mockAdsService: MockAdsService,
mockEventType: MockEventType;
beforeEach(() => {
mockAdsService = new MockAdsService();
mockEventType = new MockEventType();
});
it('listenToRender fired correctly', () => {
mockAdsService.render.next(mockEventType);
component.listenToRender();
expect(component.currentEvent).toEqual(mockEventType);
});
});
Do I need to set up something in subject.next for passing the filter?
It's very simple. You're subscribing your component after your event has already happened. It's too late for cold observable. Just switch render.next() and component.listenToRender() calls and everything should work just fine:
it('listenToRender fired correctly', () => {
component.listenToRender();
mockAdsService.render.next(mockEventType);
expect(component.currentEvent).toEqual(mockEventType);
});
I am writing some tests for an angular 2 RC application and I'm having some issues with the testing of observables. I mocked up the method setting it's type as observable but when the unit being tested tries to subscribe to the mocked observable I get an error 'Cannot read property 'subscribe' of undefined'
I'm testing my DashboardComponent, which injects a model3DService and calls model3DService.get3DModels() which is an observable that does an http request and returns an array of 3D model objects.
Here's some sample code:
Dashboard Component
import { Model3DService } from '../../services/model3D/model3D.service';
import { ProjectService } from '../../services/project/project.service';
#Component({
selector: 'cmg-dashboard',
styles: [require('./css/dashboard.scss')],
template: require('./dashboard.html')
})
export class DashboardComponent implements OnInit {
constructor(
private projectService: ProjectService,
private model3DService: Model3DService
) { }
ngOnInit (): void {
this.model3DService.get3DModels().subscribe((res: any[]) => {
this.findProjects(res);
this.models = res;
this.projectService.isProjectSelected = true;
this.createProject(res[0]);
});
}
}
Model3DService
#Injectable()
export class Model3DService {
private models: any[] = [];
public get3DModels (): Observable<any> {
return this.http.get('../../../json/3DModel.json')
.map(( res: Response ) => {
this.models = res.json();
return this.models;
});
}
}
Okay now that we have the under test heres the test I'm writing.
Dashboard Component Spec
class MockModel3DService {
public get3DModels(): Observable<any> {
return;
}
}
describe('Dashboard Component', () => {
beforeEachProviders(() => {
return [
DashboardComponent,
provide(ProjectService, {
useClass: MockProjectService
}),
provide(Model3DService, {
useClass: MockModel3DService
})
];
});
describe('ngOnInit', () => {
it('should call model3DService.get3DModels on init', (inject([DashboardComponent], (dashboardComponent: DashboardComponent, model3DService: MockModel3DService) => {
dashboardComponent.ngOnInit();
expect(model3DService.get3DModels).toHaveBeenCalled();
})));
});
});
The concept is similar to testing AngularJS $q promise. Stubbed method returns an observable mock. The method can return a subject instead which inherits Observable but also has properties of both observables and observers.
A fresh subject can be provided with mocked value in-place, a mocked promise would be required to be defined beforehand (subjects share this property with deferreds, see the relevant question).
RxJS 4 subjects have hasObservers method which obviates subscribe spy. RxJS 5 subjects miss the method, yet they expose observers property.
Most likely it should be something like that
let subject: Subject;
class MockModel3DService {
public get3DModels(): Observable<any> {
return subject;
}
}
...
// beforeEach(...)
subject = new Subject;
...
// it(...)
const models = ['mocked'];
dashboardComponent.ngOnInit();
expect(subject.observers.length).toBe(1);
subject.next(models);
expect(model3DService.get3DModels).toHaveBeenCalled();
expect(dashboardComponent.models).toBe(models);
...
Your MockModel3DServic.get3DModels does not return a observable.
import { of } from 'rxjs';
class MockModel3DService {
public get3DModels(): Observable<any> {
return of(yourResponse)
}
}