[Flutter] 채팅앱 만들기

무엇을 해야 합니까?

MessageService 생성 및 테스트 코드 작성


참조


메시지 모델

/모델/message_model.dart

  • from : 메시지를 보낸 사용자의 ID
  • to: 메시지를 받는 사용자의 ID
import 'package:flutter/foundation.dart';

class Message {
  String get id => _id;
  String _id;

  /// from : sender id
  /// to : receiver id
  final String from;
  final String to;
  final DateTime timestamp;
  final String contents;

  Message(
      {@required this.from,
      @required this.to,
      @required this.timestamp,
      @required this.contents});

  toJson() =>
      {'from': from, 'to': to, 'timestamp': timestamp, 'contents': contents};

  factory Message.fromJson(Map<String, dynamic> json) {
    var message = Message(
        from: json('from'),
        to: json('to'),
        timestamp: json('timestamp'),
        contents: json('contents'));
    message._id = json('id');
    return message;
  }
}

메시지 서비스

/서비스/message_service_impl.dart

  • 메시징 서비스에서 구현해야 하는 메소드는 send, messages 및 dispose입니다.
  • 보내기: 메시지 보내기
  • 메시지: 특정 사용자로부터 받은 메시지를 반환합니다.
  • 버리다 : 화합물을 버리다
import 'package:flutter/cupertino.dart';
import 'package:flutter_prj/model/message_model.dart';

import '../model/user_model.dart';

abstract class IMessageService {
  Future<bool> send(Message message);

  Stream<Message> messages({@required User user});

  dispose();
}

/서비스/user_service.dart

import 'dart:async';

import 'package:flutter_prj/model/message_model.dart';
import 'package:flutter_prj/model/user_model.dart';
import 'package:flutter_prj/service/message_service_impl.dart';
import 'package:logger/logger.dart';
import 'package:rethinkdb_dart/rethinkdb_dart.dart';

class MessageService implements IMessageService {
  final Connection _connection;
  final Rethinkdb _db;
   
  // 에러 발생시 로깅
  var _logger = Logger();

  final _controller = StreamController<Message>.broadcast();
  StreamSubscription _changefeed;

  MessageService(this._db, this._connection);

  @override
  dispose() {
    // 구독 취소
    _changefeed?.cancel();
    _controller?.close();
  }

  @override
  Stream<Message> messages({User user}) {
    _startReceivingMessages(user);
    return _controller.stream;
  }

  @override
  Future<bool> send(Message message) async {
    // Message를 받아서 데이터베이스에 저장
    Map record =
    await _db.table('messages').insert(message.toJson()).run(_connection);
    // DB에 데이터가 잘 들어갔는지 여부를 반환
    return record('inserted') == 1;
  }

  _startReceivingMessages(User user) {
    _changefeed = _db
        .table('messages')
        .filter({'to': user.id})
        .changes({'include_initial': true})
        .run(_connection)
        .asStream()
        .cast<Feed>()
        .listen((event) {
      event
          .forEach((feedData) {
        if (feedData('new_val') == null) return;

        final message = _messageFromFeed(feedData);
        _controller.sink.add(message);
        _removeDeliveredMessage(message);
      })
          .catchError((err) => _logger.e(err))
          .onError((err, stackTrace) => _logger.e(err));
    });
  }

  Message _messageFromFeed(feedData) {
    return Message.fromJson(feedData('new_val'));
  }

  _removeDeliveredMessage(Message message) {
    _db
        .table('messages')
        .get(message.id)
        .delete({'return_changes': false}).run(_connection);
  }
}

테스트 코드

테스트/util_for_test.dart

  • 테스트 시작시 △ test라는 데이터베이스 생성 △ 사용자 테이블 생성 메시지 테이블 생성
  • 시험이 끝날 때 △ 사용자 테이블 삭제 메시지 테이블 지우기
import 'package:rethinkdb_dart/rethinkdb_dart.dart';

Future<void> createDatabase(Rethinkdb db, Connection connection) async {
  await db.dbCreate('test').run(connection).catchError((err) => {});
  await db.tableCreate('users').run(connection).catchError((err) => {});
  await db.tableCreate('messages').run(connection).catchError((err) => {});
}

Future<void> cleanDatabase(Rethinkdb db, Connection connection) async {
  await db.table('users').delete().run(connection);
  await db.table('messages').delete().run(connection);
}

테스트/message_service_test.dart

import 'package:flutter_prj/model/message_model.dart';
import 'package:flutter_prj/model/user_model.dart';
import 'package:flutter_prj/service/message_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rethinkdb_dart/rethinkdb_dart.dart';

import 'util_for_test.dart';

void main() {
  Rethinkdb _db = Rethinkdb();
  Connection _connection;
  MessageService sut;

  setUp(() async {
    _connection = await _db.connect(host: '127.0.0.1', port: 28015);
    await createDatabase(_db, _connection);
    sut = MessageService(_db, _connection);
  });

  tearDown(() async {
    sut.dispose();
    await cleanDatabase(_db, _connection);
  });

  final sender =
      User.fromJson({'id': '1', 'active': true, 'lastSeen': DateTime.now()});

  final receiver =
      User.fromJson({'id': '2', 'active': true, 'lastSeen': DateTime.now()});

  test('if message sent successfully, then return true', () async {
    Message message = Message(
        from: sender.id,
        to: receiver.id,
        timestamp: DateTime.now(),
        contents: 'test message');
    final result = await sut.send(message);
    expect(result, true);
  });

  test('sending message works successfully', () async {
    sut.messages(user: receiver).listen(expectAsync1((message) {
          expect(message.to, receiver.id);
          expect(message.id, isNotEmpty);
        }, count: 2));
    Message firstMessage = Message(
        from: sender.id,
        to: receiver.id,
        timestamp: DateTime.now(),
        contents: 'test message');
    Message secondMessage = Message(
        from: sender.id,
        to: receiver.id,
        timestamp: DateTime.now(),
        contents: 'test message');
    await sut.send(firstMessage);
    await sut.send(secondMessage);
  });

  test('successfully subscribe and receive messages', () async {
    sut.messages(user: receiver).listen(expectAsync1((message) {
          expect(message.to, receiver.id);
          expect(message.id, isNotEmpty);
        }, count: 2));

    Message message = Message(
      from: sender.id,
      to: receiver.id,
      timestamp: DateTime.now(),
      contents: 'this is a message',
    );

    Message secondMessage = Message(
      from: sender.id,
      to: receiver.id,
      timestamp: DateTime.now(),
      contents: 'this is another message',
    );

    await sut.send(message);
    await sut.send(secondMessage);
  });

  test('successfully subscribe and receive new messages ', () async {
    Message message = Message(
      from: sender.id,
      to: receiver.id,
      timestamp: DateTime.now(),
      contents: 'this is a message',
    );

    Message secondMessage = Message(
      from: sender.id,
      to: receiver.id,
      timestamp: DateTime.now(),
      contents: 'this is another message',
    );

    await sut.send(message);
    await sut.send(secondMessage).whenComplete(
          () => sut.messages(user: receiver).listen(
                expectAsync1((message) {
                  expect(message.to, receiver.id);
                }, count: 2),
              ),
        );
  });
}

테스트 실행

Docker 컨테이너 실행

docker run -d -p 8080:8080 -p 20815:20815 rethinkdb

테스트 코드 실행


테스트 성공