1684419446
http_mock_adapter
is a simple to use mocking package for Dio intended to be used in tests. It provides various types and methods to declaratively mock request-response communication.
Here is a very basic usage scenario:
import 'package:dio/dio.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
void main() async {
final dio = Dio(BaseOptions());
final dioAdapter = DioAdapter(dio: dio);
const path = 'https://example.com';
dioAdapter.onGet(
path,
(server) => server.reply(
200,
{'message': 'Success!'},
// Reply would wait for one-sec before returning data.
delay: const Duration(seconds: 1),
),
);
final response = await dio.get(path);
print(response.data); // {message: Success!}
}
The intended usage domain is in tests when trying to simulate behavior of request-response communication with a server. The example portrays a decent use case of how one might make good use of the package.
You can quickly install the package from the command-line:
With dart
:
$ dart pub add --dev http_mock_adapter
...
With flutter
:
$ flutter pub add --dev http_mock_adapter
...
Add this to your package's pubspec.yaml
file:
dev_dependencies:
http_mock_adapter: ^0.4.4
You can then install the package from the command-line:
With dart
:
$ dart pub get
...
With flutter
:
$ flutter pub get
...
Alternatively, your editor might support dart pub get
or flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:http_mock_adapter/http_mock_adapter.dart';
All notable changes to this project will be documented in the CHANGELOG.md file.
See the AUTHORS file for information regarding the authors of the project.
http-mock-adapter is licensed under the permissive MIT License (LICENSE).
For information regarding contributions, please refer to CONTRIBUTING.md file.
Run this command:
With Dart:
$ dart pub add http_mock_adapter
With Flutter:
$ flutter pub add http_mock_adapter
This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get
):
dependencies:
http_mock_adapter: ^0.4.4
Alternatively, your editor might support dart pub get
or flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:http_mock_adapter/http_mock_adapter.dart';
import 'package:dio/dio.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import 'package:test/test.dart';
void main() async {
late Dio dio;
late DioAdapter dioAdapter;
Response<dynamic> response;
group('Accounts', () {
const baseUrl = 'https://example.com';
const userCredentials = <String, dynamic>{
'email': 'test@example.com',
'password': 'password',
};
setUp(() {
dio = Dio(BaseOptions(baseUrl: baseUrl));
dioAdapter = DioAdapter(
dio: dio,
// [FullHttpRequestMatcher] is a default matcher class
// (which actually means you haven't to pass it manually) that matches entire URL.
//
// Use [UrlRequestMatcher] for matching request based on the path of the URL.
//
// Or create your own http-request matcher via extending your class from [HttpRequestMatcher].
// See -> issue:[124] & pr:[125]
matcher: const FullHttpRequestMatcher(),
);
});
test('signs up user', () async {
const route = '/signup';
dioAdapter.onPost(
route,
(server) => server.reply(
201,
null,
// Adds one-sec delay to reply method.
// Basically, I'd wait for one second before returning reply data.
// See -> issue:[106] & pr:[126]
delay: const Duration(seconds: 1),
),
data: userCredentials,
);
// Returns a response with 201 Created success status response code.
response = await dio.post(route, data: userCredentials);
expect(response.statusCode, 201);
});
test('signs in user and fetches account information', () async {
const signInRoute = '/signin';
const accountRoute = '/account';
const accessToken = <String, dynamic>{
'token': 'ACCESS_TOKEN',
};
final headers = <String, dynamic>{
'Authentication': 'Bearer $accessToken',
};
const userInformation = <String, dynamic>{
'id': 1,
'email': 'test@example.com',
'password': 'password',
'email_verified': false,
};
dioAdapter
..onPost(
signInRoute,
(server) => server.throws(
401,
DioError(
requestOptions: RequestOptions(
path: signInRoute,
),
),
),
)
..onPost(
signInRoute,
(server) => server.reply(200, accessToken),
data: userCredentials,
)
..onGet(
accountRoute,
(server) => server.reply(200, userInformation),
headers: headers,
);
// Throws without user credentials.
expect(
() async => await dio.post(signInRoute),
throwsA(isA<DioError>()),
);
// Returns an access token if user credentials are provided.
response = await dio.post(signInRoute, data: userCredentials);
expect(response.data, accessToken);
// Returns user information if an access token is provided in headers.
response = await dio.get(
accountRoute,
options: Options(headers: headers),
);
expect(response.data, userInformation);
});
});
}
Download Details:
Author: lomsa.com
Source Code: https://github.com/lomsa-dev/http-mock-adapter
1683094818
Postman — это мощный инструмент, который можно использовать в качестве фиктивного сервера для создания фиктивных служб, доступных как в общедоступных, так и в частных сетях. Это делает его очень полезным инструментом для создания фиктивных сервисов, которые можно использовать для тестирования пользовательского интерфейса и других компонентов, не дожидаясь готовности фактического сервиса. Имитируя поведение реальной службы, Postman позволяет разработчикам тестировать свой код в реальной среде, помогая выявлять потенциальные проблемы до развертывания службы.
После того, как ваш фиктивный сервер запущен и запущен, вы можете использовать его для тестирования запросов API, отправляя их на URL-адрес, предоставленный фиктивным сервером. Ответы, которые вы получите, будут основаны на поведении, которое вы указали при настройке фиктивного сервера. Это позволяет вам тестировать ваши запросы API, не дожидаясь готовности фактического сервиса.
Я надеюсь, что из приведенного выше объяснения вы узнали, как создавать фиктивные службы API с помощью фиктивного сервера Postman.
Оригинальный источник статьи: https://www.c-sharpcorner.com/
1683091080
Postman 是一个强大的工具,可以用作模拟服务器来创建可通过公共和专用网络访问的模拟服务。这使它成为创建模拟服务的非常有用的工具,可用于测试 UI 和其他组件,而无需等待实际服务准备就绪。通过模拟真实服务的行为,Postman 允许开发人员针对真实环境测试他们的代码,帮助在部署服务之前识别潜在问题。
一旦您的模拟服务器启动并运行,您就可以使用它来测试您的 API 请求,方法是将它们发送到模拟服务器提供的 URL。您收到的响应将基于您在设置模拟服务器时指定的行为。这使您可以测试 API 请求,而无需等待实际服务准备就绪。
我希望从上面的解释中你已经学会了如何使用 Postman 模拟服务器创建模拟 API 服务。
文章原文出处:https: //www.c-sharpcorner.com/
1683087305
The Postman is a powerful tool that can be used as a mock server to create mock services that are accessible over both public and private networks. This makes it a very useful tool for creating mock services that can be used to test the UI and other components without having to wait for the actual service to be ready. By simulating the behaviour of a real service, Postman allows developers to test their code against a realistic environment, helping to identify potential issues before the service is deployed.
Once your mock server is up and running, you can use it to test your API requests by sending them to the URL provided by the mock server. The responses you receive will be based on the behaviour you specified when you set up the mock server. This allows you to test your API requests without having to wait for the actual service to be ready.
I hope from the above explanation you have learned how to create mock API services using the Postman mock server.
Original article source at: https://www.c-sharpcorner.com/
1680799237
Mock library for Dart inspired by mockito.
Mocktail focuses on providing a familiar, simple API for creating mocks in Dart (with null-safety) without the need for manual mocks or code generation.
import 'package:mocktail/mocktail.dart';
// A Real Cat class
class Cat {
String sound() => 'meow!';
bool likes(String food, {bool isHungry = false}) => false;
final int lives = 9;
}
// A Mock Cat class
class MockCat extends Mock implements Cat {}
void main() {
// Create a Mock Cat instance
final cat = MockCat();
}
The MockCat
instance can then be used to stub and verify calls.
// Stub the `sound` method.
when(() => cat.sound()).thenReturn('meow');
// Verify no interactions have occurred.
verifyNever(() => cat.sound());
// Interact with the mock cat instance.
cat.sound();
// Verify the interaction occurred.
verify(() => cat.sound()).called(1);
// Interact with the mock instance again.
cat.sound();
// Verify the interaction occurred twice.
verify(() => cat.sound()).called(2);
// Stub a method before interacting with the mock.
when(() => cat.sound()).thenReturn('purrr!');
expect(cat.sound(), 'purrr!');
// You can interact with the mock multiple times.
expect(cat.sound(), 'purrr!');
// You can change the stub.
when(() => cat.sound()).thenReturn('meow!');
expect(cat.sound(), 'meow');
// You can stub getters.
when(() => cat.lives).thenReturn(10);
expect(cat.lives, 10);
// You can stub a method for specific arguments.
when(() => cat.likes('fish', isHungry: false)).thenReturn(true);
expect(cat.likes('fish', isHungry: false), isTrue);
// You can verify the interaction for specific arguments.
verify(() => cat.likes('fish', isHungry: false)).called(1);
// You can stub a method using argument matcher: `any`.
// When stubbing a positional argument, use `any()`.
// When stubbing a named argument, use `any(named: '<argName>`)`.
// A custom matcher can be provided using `any(that: customMatcher)`.
when(() => cat.likes(any(), isHungry: any(named: 'isHungry', that: isFalse)).thenReturn(true);
expect(cat.likes('fish', isHungry: false), isTrue);
// You can stub a method to throw.
when(() => cat.sound()).thenThrow(Exception('oops'));
expect(() => cat.sound(), throwsA(isA<Exception>()));
// You can calculate stubs dynamically.
final sounds = ['purrr', 'meow'];
when(() => cat.sound()).thenAnswer((_) => sounds.removeAt(0));
expect(cat.sound(), 'purrr');
expect(cat.sound(), 'meow');
// You can capture any argument.
when(() => cat.likes('fish')).thenReturn(true);
expect(cat.likes('fish'), isTrue);
final captured = verify(() => cat.likes(captureAny())).captured;
expect(captured.last, equals(['fish']));
// You can capture a specific argument based on a matcher.
when(() => cat.likes(any())).thenReturn(true);
expect(cat.likes('fish'), isTrue);
expect(cat.likes('dog food'), isTrue);
final captured = verify(() => cat.likes(captureAny(that: startsWith('d')))).captured;
expect(captured.last, equals(['dog food']));
reset(cat); // Reset stubs and interactions
Mocktail uses closures to handle catching TypeError
instances which would otherwise propagate and cause test failures when stubbing/verifying non-nullable return types. Check out #24 for more information.
In order to support argument matchers such as any
and captureAny
mocktail has to register default fallback values to return when the argument matchers are used. Out of the box, it automatically handles all primitive types, however, when using argument matchers in place of custom types developers must use registerFallbackValue
to provide a default return value. It is only required to call registerFallbackValue
once per type so it is recommended to place all registerFallbackValue
calls within setUpAll
.
class Food {...}
class Cat {
bool likes(Food food) {...}
}
...
class MockCat extends Mock implements Cat {}
class FakeFood extends Fake implements Food {}
void main() {
setUpAll(() {
registerFallbackValue(FakeFood());
});
test('...', () {
final cat = MockCat();
when(() => cat.likes(any()).thenReturn(true);
...
});
}
This is likely due to differences in the function signature of toString
for the class and can be resolved using a mixin as demonstrated below:
mixin DiagnosticableToStringMixin on Object {
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return super.toString();
}
}
class FakeThemeData extends Fake
with DiagnosticableToStringMixin
implements ThemeData {}
Extension methods cannot be stubbed/verified as they are treated like static methods. This means that calls go directly to the extension method without caring about the instance. As a result, stubs and verify calls to extensions always result in an invocation of the real extension method.
Instead of stubbing/verifying extension methods directly, prefer to stub/verify public members on the instance with which the extension methods interact.
By default when a class extends Mock
any unstubbed methods return null
.
For example, take the following class:
class Person {
Future<void> sleep() {
await Future<void>.delayed(Duration(hours: 8));
}
}
We can create a MockPerson
like:
class MockPerson extends Mock implements Person {}
If we have code that invokes sleep
on MockPerson
we will get a TypeError
:
type 'Null' is not a subtype of type 'Future<void>'
This is because we did not stub sleep
so when sleep
is called on an instance of MockPerson
, mocktail
returns null
which is not compatible with Future<void>
.
To address this, we must explicitly stub sleep
like:
final person = MockPerson();
when(person.sleep).thenAnswer((_) async {});
Run this command:
With Dart:
$ dart pub add mocktail
With Flutter:
$ flutter pub add mocktail
This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get
):
dependencies:
mocktail: ^0.3.0
Alternatively, your editor might support dart pub get
or flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:mocktail/mocktail.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';
class Food {}
class Chicken extends Food {}
class Tuna extends Food {}
// A Real Cat class
class Cat {
String sound() => 'meow!';
bool likes(String food, {bool isHungry = false}) => false;
void eat<T extends Food>(T food) {}
final int lives = 9;
}
// A Mock Cat class
class MockCat extends Mock implements Cat {}
void main() {
group('Cat', () {
setUpAll(() {
// Register fallback values when using
// `any` or `captureAny` with custom objects.
registerFallbackValue(Chicken());
registerFallbackValue(Tuna());
});
late Cat cat;
setUp(() {
cat = MockCat();
});
test('example', () {
// Stub a method before interacting with the mock.
when(() => cat.sound()).thenReturn('purr');
// Interact with the mock.
expect(cat.sound(), 'purr');
// Verify the interaction.
verify(() => cat.sound()).called(1);
// Stub a method with parameters
when(
() => cat.likes('fish', isHungry: any(named: 'isHungry')),
).thenReturn(true);
expect(cat.likes('fish', isHungry: true), isTrue);
// Verify the interaction.
verify(() => cat.likes('fish', isHungry: true)).called(1);
// Interact with the mock.
cat
..eat(Chicken())
..eat(Tuna());
// Verify the interaction with specific type arguments.
verify(() => cat.eat<Chicken>(any())).called(1);
verify(() => cat.eat<Tuna>(any())).called(1);
verifyNever(() => cat.eat<Food>(any()));
});
});
}
Download Details:
Author: felangel.dev
Source Code: https://github.com/felangel/mocktail
1680411900
Безопасно протестируйте свой код с помощью макетов.
1 апреля — это фальшивые истории и притворство. Это делает этот день идеальным, чтобы поговорить о насмешках.
Иногда использование реальных объектов затруднено, опрометчиво или сложно. Например, requests.Sessionподключается к реальным веб-сайтам. Использование его в ваших юнит-тестах вызывает… много… проблем.
«Моки» — это концепция юниттеста. Они производят объекты, заменяющие настоящие.
from unittest import mock
Существует целая индустрия, объясняющая, что «фиктивный», «фальшивый» и «заглушка» слегка различаются. В этой статье я использую эти термины взаимозаменяемо.
regular = mock.MagicMock()
def do_something(o):
return o.something(5)
do_something(regular)
Этот код производит:
<MagicMock name='mock.something()' id='140228824621520'>
Моки имеют все методы. Методы обычно возвращают другой Mock. Это можно изменить, назначив его return_value.
Например, предположим, что вы хотите вызвать следующую функцию:
def do_something(o):
return o.something() + 1
Это требует чего-то, что имеет .something()метод. К счастью, у фиктивных объектов это есть:
obj = mock.MagicMock(name="an object")
obj.something.return_value = 2
print(do_something(obj))
Ответ:
3
Также возможно переопределить «магические методы»:
a = mock.MagicMock()
a.__str__.return_value = "an a"
print(str(a))
Ответ:
an a
Убедитесь, что макет не имеет «лишних» методов или атрибутов, используя спецификацию. Например, вот код, который должен дать сбой:
import pathlib
def bad_pathlib_usage(path):
## TYPO: missing underscore
path.writetext("hello")
dummy_path = mock.MagicMock(spec=pathlib.Path)
try:
bad_pathlib_usage(dummy_path)
except Exception as exc:
print("Failed!", repr(exc))
Результат:
Failed! AttributeError("Mock object has no attribute 'writetext'")
Иногда наличие , MagicMockкоторое каждый раз возвращает одно и то же, не совсем то, что вам нужно. Например, sys.stdin.readline()обычно возвращает разные значения, а не одно и то же значение на протяжении всего теста.
Свойство side_effectпозволяет контролировать то, что возвращает магический макет, на более детальном уровне, чем использование return_value.
Одной из вещей, которым можно присвоить, side_effectявляется итерируемый объект , такой как последовательность или генератор.
Это мощная функция. Это позволяет контролировать возвращаемое значение каждого вызова с небольшим количеством кода.
different_things = mock.MagicMock()
different_things.side_effect = [1, 2, 3]
print(different_things())
print(different_things())
print(different_things())
Выход:
1
2
3
Более реалистичным примером является имитация ввода файла. В этом случае я хочу иметь возможность контролировать, что readlineвозвращается каждый раз, чтобы притвориться, что это ввод файла:
def parse_three_lines(fpin):
line = fpin.readline()
name, value = line.split()
modifier = fpin.readline().strip()
extra = fpin.readline().strip()
return {name: f"{value}/{modifier}+{extra}"}
from io import TextIOBase
filelike = mock.MagicMock(spec=TextIOBase)
filelike.readline.side_effect = [
"thing important\n",
"a-little\n",
"to-some-people\n"
]
value = parse_three_lines(filelike)
print(value)
Результат:
{'thing': 'important/a-little+to-some-people'}
Еще одна возможная вещь — назначить исключение для side_effectатрибута. Это приводит к тому, что вызов вызывает назначенное вами исключение. Использование этой функции позволяет моделировать граничные условия в среде, обычно именно такие, которые:
Одним из популярных случаев являются проблемы с сетью. Согласно закону Мерфи, они всегда происходят в 4 часа утра, вызывая срабатывание пейджера, и никогда в 10 часов, когда вы сидите за своим столом. Нижеследующее основано на реальном коде, который я написал для тестирования сетевой службы.
В этом упрощенном примере код возвращает длину строки ответа или отрицательное число, если истекло время ожидания. Число отличается в зависимости от того, когда оно было достигнуто при согласовании протокола. Это позволяет коду отличать, например, «время ожидания соединения» от «время ожидания ответа».
Тестировать этот код на реальном сервере сложно. Серверы изо всех сил стараются избегать простоев! Вы можете разветвить код C сервера и добавить немного хаоса, или вы можете просто использовать side_effectи издеваться:
import socket
def careful_reader(sock):
sock.settimeout(5)
try:
sock.connect(("some.host", 8451))
except socket.timeout:
return -1
try:
sock.sendall(b"DO THING\n")
except socket.timeout:
return -2
fpin = sock.makefile()
try:
line = fpin.readline()
except socket.timeout:
return -3
return len(line.strip())
from io import TextIOBase
from unittest import mock
sock = mock.MagicMock(spec=socket.socket)
sock.connect.side_effect = socket.timeout("too long")
print(careful_reader(sock))
Результат — сбой, что в данном случае означает успешный тест:
-1
С осторожными побочными эффектами вы можете добраться до каждого из возвращаемых значений. Например:
sock = mock.MagicMock(spec=socket.socket)
sock.sendall.side_effect = socket.timeout("too long")
print(careful_reader(sock))
Результат:
-2
Предыдущий пример упрощен. Тестовый код реальной сетевой службы должен проверять правильность полученных результатов, чтобы убедиться, что сервер работает правильно. Это означает выполнение синтетического запроса и поиск правильного результата. Мок-объект должен подражать этому. Он должен выполнять некоторые вычисления на входах.
Попытка протестировать такой код без выполнения каких-либо вычислений затруднительна. Тесты имеют тенденцию быть слишком нечувствительными или слишком «ненадежными».
Здесь мой код неверен. Нечувствительный тест его не улавливает, а нестабильный тест провалится, даже если он будет исправлен!
import socket
import random
def yolo_reader(sock):
sock.settimeout(5)
sock.connect(("some.host", 8451))
fpin = sock.makefile()
order = [0, 1]
random.shuffle(order)
while order:
if order.pop() == 0:
sock.sendall(b"GET KEY\n")
key = fpin.readline().strip()
else:
sock.sendall(b"GET VALUE\n")
value = fpin.readline().strip()
return {value: key} ## Woops bug, should be {key: value}
Следующее было бы слишком «бесчувственным», не обнаруживая ошибку:
sock = mock.MagicMock(spec=socket.socket)
sock.makefile.return_value.readline.return_value = "interesting\n"
assert yolo_reader(sock) == {"interesting": "interesting"}
Следующее было бы слишком «ненадежным», обнаруживая ошибку, даже если ее нет, иногда:
for i in range(10):
sock = mock.MagicMock(spec=socket.socket)
sock.makefile.return_value.readline.side_effect = ["key\n", "value\n"]
if yolo_reader(sock) != {"key": "value"}:
print(i, end=" ")
Например:
3 6 7 9
Последний вариант получения результатов от фиктивного объекта — назначить вызываемый объект side_effect. Это призывает side_effectпросто назвать это. Почему бы просто не назначить вызываемый объект непосредственно атрибуту? Наберитесь терпения, я доберусь до этого в следующей части!
В этом примере мой вызываемый объект (просто функция) присваивает a return_valueатрибуту другого объекта. Это не такая уж редкость. Я моделирую среду, а в реальной среде нажатие на одну вещь часто влияет на другие вещи.
sock = mock.MagicMock(spec=socket.socket)
def sendall(data):
cmd, name = data.decode("ascii").split()
if name == "KEY":
sock.makefile.return_value.readline.return_value = "key\n"
elif name == "VALUE":
sock.makefile.return_value.readline.return_value = "value\n"
else:
raise ValueError("got bad command", name)
sock.sendall.side_effect = sendall
print(yolo_reader(sock), dict(key="value"))
Результат:
{'value': 'key'} {'key': 'value'}
При написании модульного теста вы находитесь «вдали» от кода, но пытаетесь заглянуть в его нутро, чтобы увидеть, как он себя ведет. Объект Mock — ваш подлый шпион. После того, как он попадает в производственный код, он точно все записывает. Вот как вы можете узнать, что делает ваш код и является ли он правильным.
Самое простое — просто убедиться, что код вызывается ожидаемое количество раз. Атрибут .call_count— это именно то, что имеет значение.
def get_values(names, client):
ret_value = []
cache = {}
for name in names:
# name = name.lower()
if name not in cache:
value = client.get(f"https://httpbin.org/anything/grab?name={name}").json()['args']['name']
cache[name] = value
ret_value.append(cache[name])
return ret_value
client = mock.MagicMock()
client.get.return_value.json.return_value = dict(args=dict(name="something"))
result = get_values(['one', 'One'], client)
print(result)
print("call count", client.get.call_count)
Результаты:
['something', 'something']
call count 2
Одним из преимуществ проверки .call_count >= 1по сравнению с проверкой .calledявляется то, что она более устойчива к глупым опечаткам.
def call_function(func):
print("I'm going to call the function, really!")
if False:
func()
print("I just called the function")
func = mock.MagicMock()
call_function(func)
print(func.callled) # TYPO -- Extra "l"
I'm going to call the function, really!
I just called the function
<MagicMock name='mock.callled' id='140228249343504'>
Усердное использование specможет предотвратить это. Однако specне является рекурсивным. Даже если исходный фиктивный объект имеет спецификацию, тест, который гарантирует, что каждый его атрибут также имеет спецификацию, встречается редко. Однако использование .call_countвместо .called— это простой хак, полностью исключающий возможность совершения этой ошибки.
В следующем примере я гарантирую, что код вызывает метод с правильными аргументами. При автоматизации манипуляций с центром обработки данных важно все делать правильно. Как говорится: «Человеку свойственно ошибаться, а чтобы разрушить целый ЦОД, нужен робот с жуком».
Мы хотим убедиться, что наша автоматизация на основе Paramiko правильно получает размеры файлов, даже если в именах файлов есть пробелы.
def get_remote_file_size(client, fname):
client.connect('ssh.example.com')
stdin, stdout, stderr = client.exec_command(f"ls -l {fname}")
stdin.close()
results = stdout.read()
errors = stderr.read()
stdout.close()
stderr.close()
if errors != '':
raise ValueError("problem with command", errors)
return int(results.split()[4])
fname = "a file"
client = mock.MagicMock()
client.exec_command.return_value = [mock.MagicMock(name=str(i)) for i in range(3)]
client.exec_command.return_value[1].read.return_value = f"""\
-rw-rw-r-- 1 user user 123 Jul 18 20:25 {fname}
"""
client.exec_command.return_value[2].read.return_value = ""
result = get_remote_file_size(client, fname)
assert result == 123
[args], kwargs = client.exec_command.call_args
import shlex
print(shlex.split(args))
Результаты:
['ls', '-l', 'a', 'file']
Упс! Это не правильная команда. Хорошо, что вы проверили аргументы.
Моки обладают большой силой. Как и любой мощный инструмент, его неправильное использование — быстрый способ попасть в большой беспорядок. Но правильно используя .return_value, .side_effect, и различные .call*свойства, можно написать лучшие модульные тесты.
Хороший юнит-тест — это тот, который:
«Качество» не бинарно. Он существует в спектре. Плохость модульного теста определяется :
При использовании макета найдите время и подумайте об обеих метриках, чтобы оценить, поможет ли вам этот макет и этот модульный тест.
Оригинальный источник статьи: https://opensource.com/
1680408120
使用模拟安全地测试您的代码。
4 月 1 日是关于假故事和假装的日子。这使它成为谈论模拟的完美日子。
有时,使用真实的对象是困难的、不明智的或复杂的。例如,arequests.Session连接到真实网站。在你的单元测试中使用它会带来很多……很多……问题。
“模拟”是一个单元测试概念。他们生产的物品是真实物品的替代品。
from unittest import mock
整个家庭手工业都会解释“模拟”、“伪造”和“存根”之间的细微差别。在本文中,我交替使用这些术语。
regular = mock.MagicMock()
def do_something(o):
return o.something(5)
do_something(regular)
此代码产生:
<MagicMock name='mock.something()' id='140228824621520'>
模拟具有所有方法。这些方法通常返回另一个 Mock。这可以通过将其分配给 来更改return_value。
例如,假设您要调用以下函数:
def do_something(o):
return o.something() + 1
它需要有.something()方法的东西。幸运的是,模拟对象拥有它:
obj = mock.MagicMock(name="an object")
obj.something.return_value = 2
print(do_something(obj))
答案:
3
也可以覆盖“魔术方法”:
a = mock.MagicMock()
a.__str__.return_value = "an a"
print(str(a))
答案:
an a
使用规范确保模拟没有“额外”方法或属性。例如,这里有一些应该失败的代码:
import pathlib
def bad_pathlib_usage(path):
## TYPO: missing underscore
path.writetext("hello")
dummy_path = mock.MagicMock(spec=pathlib.Path)
try:
bad_pathlib_usage(dummy_path)
except Exception as exc:
print("Failed!", repr(exc))
结果:
Failed! AttributeError("Mock object has no attribute 'writetext'")
有时,MagicMock每次都返回相同的东西并不完全是您需要的。例如,sys.stdin.readline()通常返回不同的值,而不是在整个测试过程中返回相同的值。
该属性side_effect允许在比使用return_value.
可以分配给的事物之一side_effect是可迭代对象,例如序列或生成器。
这是一个强大的功能。它允许用很少的代码控制每个调用的返回值。
different_things = mock.MagicMock()
different_things.side_effect = [1, 2, 3]
print(different_things())
print(different_things())
print(different_things())
输出:
1
2
3
一个更现实的例子是在模拟文件输入时。在这种情况下,我希望能够控制readline每次返回的内容以假装它是文件输入:
def parse_three_lines(fpin):
line = fpin.readline()
name, value = line.split()
modifier = fpin.readline().strip()
extra = fpin.readline().strip()
return {name: f"{value}/{modifier}+{extra}"}
from io import TextIOBase
filelike = mock.MagicMock(spec=TextIOBase)
filelike.readline.side_effect = [
"thing important\n",
"a-little\n",
"to-some-people\n"
]
value = parse_three_lines(filelike)
print(value)
结果:
{'thing': 'important/a-little+to-some-people'}
另一件可能的事情是为side_effect属性分配一个例外。这会导致调用引发您分配的异常。使用此功能可以模拟环境中的边缘条件,通常恰好是:
一种常见的情况是网络问题。根据墨菲定律,它们总是在凌晨 4 点发生,导致寻呼机响起,而当您坐在办公桌前时,它们绝不会发生在上午 10 点。下面是基于我编写的测试网络服务的真实代码。
在这个简化的示例中,代码返回响应行的长度,如果达到超时则返回负数。该数字根据协议协商达成的时间而有所不同。例如,这允许代码区分“连接超时”和“响应超时”。
针对真实服务器测试此代码很困难。服务器努力避免中断!您可以分叉服务器的 C 代码并添加一些混乱,或者您可以只使用side_effect和模拟:
import socket
def careful_reader(sock):
sock.settimeout(5)
try:
sock.connect(("some.host", 8451))
except socket.timeout:
return -1
try:
sock.sendall(b"DO THING\n")
except socket.timeout:
return -2
fpin = sock.makefile()
try:
line = fpin.readline()
except socket.timeout:
return -3
return len(line.strip())
from io import TextIOBase
from unittest import mock
sock = mock.MagicMock(spec=socket.socket)
sock.connect.side_effect = socket.timeout("too long")
print(careful_reader(sock))
结果是失败,在这种情况下意味着测试成功:
-1
通过仔细的副作用,您可以获得每个返回值。例如:
sock = mock.MagicMock(spec=socket.socket)
sock.sendall.side_effect = socket.timeout("too long")
print(careful_reader(sock))
结果:
-2
前面的例子被简化了。真正的网络服务测试代码必须验证它得到的结果是否正确,以验证服务器是否正常工作。这意味着进行综合请求并寻找正确的结果。模拟对象必须模拟它。它必须对输入执行一些计算。
尝试在不执行任何计算的情况下测试此类代码是很困难的。这些测试往往过于不敏感或过于“古怪”。
在这里,我的代码不正确。insensitive 测试不会捕获它,而 flakey 测试即使修复也会失败!
import socket
import random
def yolo_reader(sock):
sock.settimeout(5)
sock.connect(("some.host", 8451))
fpin = sock.makefile()
order = [0, 1]
random.shuffle(order)
while order:
if order.pop() == 0:
sock.sendall(b"GET KEY\n")
key = fpin.readline().strip()
else:
sock.sendall(b"GET VALUE\n")
value = fpin.readline().strip()
return {value: key} ## Woops bug, should be {key: value}
以下内容太“不敏感”,无法检测到错误:
sock = mock.MagicMock(spec=socket.socket)
sock.makefile.return_value.readline.return_value = "interesting\n"
assert yolo_reader(sock) == {"interesting": "interesting"}
有时,即使错误不存在,以下内容也太“古怪”了:
for i in range(10):
sock = mock.MagicMock(spec=socket.socket)
sock.makefile.return_value.readline.side_effect = ["key\n", "value\n"]
if yolo_reader(sock) != {"key": "value"}:
print(i, end=" ")
例如:
3 6 7 9
从模拟对象获取结果的最后一个选项是将一个可调用对象分配给side_effect. 这调用side_effect简单地调用它。为什么不直接将可调用对象分配给属性呢?耐心点,我会在下一部分讲到!
在此示例中,我的可调用对象(只是一个函数)将 a 分配return_value给另一个对象的属性。这并不少见。我是在模拟环境,在真实环境中,戳一个东西往往会影响到其他东西。
sock = mock.MagicMock(spec=socket.socket)
def sendall(data):
cmd, name = data.decode("ascii").split()
if name == "KEY":
sock.makefile.return_value.readline.return_value = "key\n"
elif name == "VALUE":
sock.makefile.return_value.readline.return_value = "value\n"
else:
raise ValueError("got bad command", name)
sock.sendall.side_effect = sendall
print(yolo_reader(sock), dict(key="value"))
结果:
{'value': 'key'} {'key': 'value'}
在编写单元测试时,您“远离”代码,但试图深入了解它的行为方式。Mock 对象是你偷偷摸摸的间谍。进入生产代码后,它忠实地记录了一切。通过这种方式,您可以找到代码的作用以及它是否正确。
最简单的事情就是确保代码被调用了预期的次数。该.call_count属性正是重要的。
def get_values(names, client):
ret_value = []
cache = {}
for name in names:
# name = name.lower()
if name not in cache:
value = client.get(f"https://httpbin.org/anything/grab?name={name}").json()['args']['name']
cache[name] = value
ret_value.append(cache[name])
return ret_value
client = mock.MagicMock()
client.get.return_value.json.return_value = dict(args=dict(name="something"))
result = get_values(['one', 'One'], client)
print(result)
print("call count", client.get.call_count)
结果:
['something', 'something']
call count 2
.call_count >= 1与检查相比,检查的一个好处.called是它更能抵抗愚蠢的拼写错误。
def call_function(func):
print("I'm going to call the function, really!")
if False:
func()
print("I just called the function")
func = mock.MagicMock()
call_function(func)
print(func.callled) # TYPO -- Extra "l"
I'm going to call the function, really!
I just called the function
<MagicMock name='mock.callled' id='140228249343504'>
勤奋地使用spec可以防止这种情况。但是,spec不是递归的。即使原始模拟对象有规范,也很少有测试确保它具有的每个属性也有规范。但是,使用.call_countinstead of.called是一种简单的技巧,可以完全消除发生此错误的机会。
在下一个示例中,我确保代码使用正确的参数调用方法。在自动化数据中心操作时,把事情做好很重要。正如他们所说,“犯错是人之常情,但摧毁整个数据中心需要一个有虫子的机器人。”
我们希望确保基于 Paramiko 的自动化能够正确获取文件的大小,即使文件名中包含空格也是如此。
def get_remote_file_size(client, fname):
client.connect('ssh.example.com')
stdin, stdout, stderr = client.exec_command(f"ls -l {fname}")
stdin.close()
results = stdout.read()
errors = stderr.read()
stdout.close()
stderr.close()
if errors != '':
raise ValueError("problem with command", errors)
return int(results.split()[4])
fname = "a file"
client = mock.MagicMock()
client.exec_command.return_value = [mock.MagicMock(name=str(i)) for i in range(3)]
client.exec_command.return_value[1].read.return_value = f"""\
-rw-rw-r-- 1 user user 123 Jul 18 20:25 {fname}
"""
client.exec_command.return_value[2].read.return_value = ""
result = get_remote_file_size(client, fname)
assert result == 123
[args], kwargs = client.exec_command.call_args
import shlex
print(shlex.split(args))
结果:
['ls', '-l', 'a', 'file']
糟糕!那不是正确的命令。幸好你检查了参数。
模拟有很大的力量。与任何强大的工具一样,使用不当会很快陷入大混乱。但正确使用.return_value、.side_effect和各种.call*属性,可以编写出最好的单元测试。
一个好的单元测试是这样的:
“质量”不是二元的。它存在于一个频谱上。单元测试的不良程度取决于:
使用 mock 时,花点时间考虑这两个指标,以评估这个 mock 和这个单元测试是否会帮助或阻碍你。
文章原文出处:https: //opensource.com/
1680393600
Test your code safely with mocks.
April 1st is all about fake stories and pretending. This makes it the perfect day to talk about mocking.
Sometimes, using real objects is hard, ill-advised, or complicated. For example, a requests.Session
connects to real websites. Using it in your unittests invites a…lot…of problems.
"Mocks" are a unittest concept. They produce objects that are substitutes for the real ones.
from unittest import mock
There's a whole cottage industry that will explain that "mock", "fake", and "stub" are all subtly different. In this article, I use the terms interchangeably.
regular = mock.MagicMock()
def do_something(o):
return o.something(5)
do_something(regular)
This code produces:
<MagicMock name='mock.something()' id='140228824621520'>
Mocks have all the methods. The methods usually return another Mock. This can be changed by assigning it to return_value
.
For example, suppose you want to call the following function:
def do_something(o):
return o.something() + 1
It requires something which has the .something()
method. Luckily, mock objects have it:
obj = mock.MagicMock(name="an object")
obj.something.return_value = 2
print(do_something(obj))
The answer:
3
It is also possible to override the "magic methods":
a = mock.MagicMock()
a.__str__.return_value = "an a"
print(str(a))
The answer:
an a
Make sure that a mock does not have "extra" methods or attributes by using a spec. For example, here's some code that should fail:
import pathlib
def bad_pathlib_usage(path):
## TYPO: missing underscore
path.writetext("hello")
dummy_path = mock.MagicMock(spec=pathlib.Path)
try:
bad_pathlib_usage(dummy_path)
except Exception as exc:
print("Failed!", repr(exc))
The result:
Failed! AttributeError("Mock object has no attribute 'writetext'")
Sometimes, having a MagicMock
that returns the same thing every time isn't quite everything you need it to be. For example, sys.stdin.readline()
usually returns different values, not the same value throughout the test.
The property side_effect
allows controlling what a magic mock returns on a more detailed level than using return_value
.
One of the things that can be assigned to side_effect
is an iterable, such as a sequence or a generator.
This is a powerful feature. It allows controlling each call's return value, with little code.
different_things = mock.MagicMock()
different_things.side_effect = [1, 2, 3]
print(different_things())
print(different_things())
print(different_things())
The output:
1
2
3
A more realistic example is when simulating file input. In this case, I want to be able to control what readline
returns each time to pretend it's file input:
def parse_three_lines(fpin):
line = fpin.readline()
name, value = line.split()
modifier = fpin.readline().strip()
extra = fpin.readline().strip()
return {name: f"{value}/{modifier}+{extra}"}
from io import TextIOBase
filelike = mock.MagicMock(spec=TextIOBase)
filelike.readline.side_effect = [
"thing important\n",
"a-little\n",
"to-some-people\n"
]
value = parse_three_lines(filelike)
print(value)
The result:
{'thing': 'important/a-little+to-some-people'}
Another thing that's possible is assigning an exception to the side_effect
attribute. This causes the call to raise the exception you assigned. Using this feature allows simulating edge conditions in the environment, usually precisely the ones that:
One popular case is network issues. As per Murphy's law, they always happen at 4 AM, causing a pager to go off, and never at 10 AM when you're sitting at your desk. The following is based on real code I wrote to test a network service.
In this simplified example, the code returns the length of the response line, or a negative number if a timeout has been reached. The number is different based on when in the protocol negotiation this has been reached. This allows the code to distinguish "connection timeout" from "response timeout", for example.
Testing this code against a real server is hard. Servers try hard to avoid outages! You could fork the server's C code and add some chaos or you can just use side_effect
and mock:
import socket
def careful_reader(sock):
sock.settimeout(5)
try:
sock.connect(("some.host", 8451))
except socket.timeout:
return -1
try:
sock.sendall(b"DO THING\n")
except socket.timeout:
return -2
fpin = sock.makefile()
try:
line = fpin.readline()
except socket.timeout:
return -3
return len(line.strip())
from io import TextIOBase
from unittest import mock
sock = mock.MagicMock(spec=socket.socket)
sock.connect.side_effect = socket.timeout("too long")
print(careful_reader(sock))
The result is a failure, which in this case means a successful test:
-1
With careful side effects, you can get to each of the return values. For example:
sock = mock.MagicMock(spec=socket.socket)
sock.sendall.side_effect = socket.timeout("too long")
print(careful_reader(sock))
The result:
-2
The previous example is simplified. Real network service test code must verify that the results it got were correct to validate that the server works correctly. This means doing a synthetic request and looking for a correct result. The mock object has to emulate that. It has to perform some computation on the inputs.
Trying to test such code without performing any computation is difficult. The tests tend to be too insensitive or too "flakey".
Here, my code is incorrect. The insensitive test does not catch it, while the flakey test would fail even if it was fixed!
import socket
import random
def yolo_reader(sock):
sock.settimeout(5)
sock.connect(("some.host", 8451))
fpin = sock.makefile()
order = [0, 1]
random.shuffle(order)
while order:
if order.pop() == 0:
sock.sendall(b"GET KEY\n")
key = fpin.readline().strip()
else:
sock.sendall(b"GET VALUE\n")
value = fpin.readline().strip()
return {value: key} ## Woops bug, should be {key: value}
The following would be too "insensitive", not detecting the bug:
sock = mock.MagicMock(spec=socket.socket)
sock.makefile.return_value.readline.return_value = "interesting\n"
assert yolo_reader(sock) == {"interesting": "interesting"}
The following would be too "flakey," detecting the bug even if it's not there, sometimes:
for i in range(10):
sock = mock.MagicMock(spec=socket.socket)
sock.makefile.return_value.readline.side_effect = ["key\n", "value\n"]
if yolo_reader(sock) != {"key": "value"}:
print(i, end=" ")
For example:
3 6 7 9
The final option of getting results from a mock object is to assign a callable object to side_effect
. This calls side_effect
to simply call it. Why not just assign a callable object directly to the attribute? Have patience, I'll get to that in the next part!
In this example, my callable object (just a function) assigns a return_value
to the attribute of another object. This isn't that uncommon. I'm simulating the environment, and in a real environment, poking one thing often affects other things.
sock = mock.MagicMock(spec=socket.socket)
def sendall(data):
cmd, name = data.decode("ascii").split()
if name == "KEY":
sock.makefile.return_value.readline.return_value = "key\n"
elif name == "VALUE":
sock.makefile.return_value.readline.return_value = "value\n"
else:
raise ValueError("got bad command", name)
sock.sendall.side_effect = sendall
print(yolo_reader(sock), dict(key="value"))
The result:
{'value': 'key'} {'key': 'value'}
When writing a unit test, you are "away" from the code but trying to peer into its guts to see how it behaves. The Mock object is your sneaky spy. After it gets into the production code, it records everything faithfully. This is how you can find what your code does and whether it's the right thing.
The simplest thing is to just make sure that the code is called the expected number of times. The .call_count
attribute is exactly what counts that.
def get_values(names, client):
ret_value = []
cache = {}
for name in names:
# name = name.lower()
if name not in cache:
value = client.get(f"https://httpbin.org/anything/grab?name={name}").json()['args']['name']
cache[name] = value
ret_value.append(cache[name])
return ret_value
client = mock.MagicMock()
client.get.return_value.json.return_value = dict(args=dict(name="something"))
result = get_values(['one', 'One'], client)
print(result)
print("call count", client.get.call_count)
The results:
['something', 'something']
call count 2
One benefit of checking .call_count >= 1
as opposed to checking .called
is that it is more resistant to silly typos.
def call_function(func):
print("I'm going to call the function, really!")
if False:
func()
print("I just called the function")
func = mock.MagicMock()
call_function(func)
print(func.callled) # TYPO -- Extra "l"
I'm going to call the function, really!
I just called the function
<MagicMock name='mock.callled' id='140228249343504'>
Using spec
diligently can prevent that. However, spec
is not recursive. Even if the original mock object has a spec, rare is the test that makes sure that every single attribute it has also has a spec. However, using .call_count
instead of .called
is a simple hack that completely eliminates the chance to make this error.
In the next example, I ensure the code calls the method with the correct arguments. When automating data center manipulations, it's important to get things right. As they say, "To err is human, but to destroy an entire data center requires a robot with a bug."
We want to make sure our Paramiko-based automation correctly gets the sizes of files, even when the file names have spaces in them.
def get_remote_file_size(client, fname):
client.connect('ssh.example.com')
stdin, stdout, stderr = client.exec_command(f"ls -l {fname}")
stdin.close()
results = stdout.read()
errors = stderr.read()
stdout.close()
stderr.close()
if errors != '':
raise ValueError("problem with command", errors)
return int(results.split()[4])
fname = "a file"
client = mock.MagicMock()
client.exec_command.return_value = [mock.MagicMock(name=str(i)) for i in range(3)]
client.exec_command.return_value[1].read.return_value = f"""\
-rw-rw-r-- 1 user user 123 Jul 18 20:25 {fname}
"""
client.exec_command.return_value[2].read.return_value = ""
result = get_remote_file_size(client, fname)
assert result == 123
[args], kwargs = client.exec_command.call_args
import shlex
print(shlex.split(args))
The results:
['ls', '-l', 'a', 'file']
Woops! That's not the right command. Good thing you checked the arguments.
Mocks have a lot of power. Like any powerful tool, using it improperly is a fast way to get into a big mess. But properly using the .return_value
, .side_effect
, and the various .call*
properties, it's possible to write the best sort of unit tests.
A good unit test is one that:
"Quality" is not binary. It exists on a spectrum. The badness of a unit test is determined by:
When using a mock, take the time and think about both metrics to evaluate whether this mock and this unit test, will help or hinder you.
Original article source at: https://opensource.com/
1678251251
Mockolo is an efficient mock generator for Swift. Swift doesn't provide mocking support, and Mockolo provides a fast and easy way to autogenerate mock objects that can be tested in your code. One of the main objectives of Mockolo is fast performance. Unlike other frameworks, Mockolo provides highly performant and scalable generation of mocks via a lightweight commandline tool, so it can run as part of a linter or a build if one chooses to do so. Try Mockolo and enhance your project's test coverage in an effective, performant way.
One of the main objectives of this project is high performance. There aren't many 3rd party tools that perform fast on a large codebase containing, for example, over 2M LoC or over 10K protocols. They take several hours and even with caching enabled take several minutes. Mockolo was built to make highly performant generation of mocks possible (in the magnitude of seconds) on such large codebase. It uses a minimal set of frameworks necessary (mentioned in the Used libraries section) to keep the code lean and efficient.
Another objective is to enable flexibility in using or overriding types if needed. This allows use of some of the features that require deeper analysis such as protocols with associated types to be simpler, more straightforward, and less fragile.
This project may contain unstable APIs which may not be ready for general use. Support and/or new releases may be limited.
Option 1: By Mint
$ mint install uber/mockolo
$ mint run uber/mockolo mockolo -h // see commandline input options below
Option 2: Homebrew
$ brew install mockolo
Option 3: Use the binary
Go to the Release tab and download/install the binary directly.
Option 4: Clone and build/run
$ git clone https://github.com/uber/mockolo.git
$ cd mockolo
$ swift build -c release
$ .build/release/mockolo -h // see commandline input options below
To call mockolo from any location, copy the executable into a directory that is part of your PATH
environment variable.
To check out a specific version,
$ git tag -l
$ git checkout [tag]
To use Xcode to build and run,
$ swift package generate-xcodeproj
Mockolo
is a commandline executable. To run it, pass in a list of the source file directories or file paths of a build target, and the destination filepath for the mock output. To see other arguments to the commandline, run mockolo --help
.
./mockolo -s myDir -d ./OutputMocks.swift -x Images Strings
This parses all the source files in myDir
directory, excluding any files ending with Images
or Strings
in the file name (e.g. MyImages.swift), and generates mocks to a file at OutputMocks.swift
in the current directory.
Use --help to see the complete argument options.
./mockolo -h // or --help
OVERVIEW: Mockolo: Swift mock generator.
USAGE: mockolo [<options>] --destination <destination>
OPTIONS:
--allow-set-call-count If set, generated *CallCount vars will be allowed to set manually.
--annotation <annotation>
A custom annotation string used to indicate if a type should be mocked (default = @mockable). (default: @mockable)
-j, --concurrency-limit <n>
Maximum number of threads to execute concurrently (default = number of cores on the running machine).
--custom-imports <custom-imports>
If set, custom module imports (separated by a space) will be added to the final import statement list.
--enable-args-history Whether to enable args history for all functions (default = false). To enable history per function, use the 'history' keyword in the annotation argument.
--disable-combine-default-values
Whether to disable generating Combine streams in mocks (default = false). Set this to true to control how your streams are created in your mocks.
--exclude-imports <exclude-imports>
If set, listed modules (separated by a space) will be excluded from the import statements in the mock output.
-x, --exclude-suffixes <exclude-suffixes>
List of filename suffix(es) without the file extensions to exclude from parsing (separated by a space).
--header <header> A custom header documentation to be added to the beginning of a generated mock file.
-l, --logging-level <n> The logging level to use. Default is set to 0 (info only). Set 1 for verbose, 2 for warning, and 3 for error. (default: 0)
--macro <macro> If set, #if [macro] / #endif will be added to the generated mock file content to guard compilation.
--mock-all If set, it will mock all types (protocols and classes) with a mock annotation (default is set to false and only mocks protocols with a mock annotation).
--mock-filelist <mock-filelist>
Path to a file containing a list of dependent files (separated by a new line) of modules this target depends on.
--mock-final If set, generated mock classes will have the 'final' attributes (default is set to false).
-mocks, --mockfiles <mocks>
List of mock files (separated by a space) from modules this target depends on. If the --mock-filelist value exists, this will be ignored.
-d, --destination <destination>
Output file path containing the generated Swift mock classes. If no value is given, the program will exit.
-s, --sourcedirs <sourcedirs>
Paths to the directories containing source files to generate mocks for (separated by a space). If the --filelist or --sourcefiles values exist, they will be ignored.
-f, --filelist <filelist>
Path to a file containing a list of source file paths (delimited by a new line). If the --sourcedirs value exists, this will be ignored.
-srcs, --sourcefiles <srcs>
List of source files (separated by a space) to generate mocks for. If the --sourcedirs or --filelist value exists, this will be ignored.
-i, --testable-imports <testable-imports>
If set, @testable import statements will be added for each module name in this list (separated by a space).
--use-mock-observable If set, a property wrapper will be used to mock RxSwift Observable variables (default is set to false).
--use-template-func If set, a common template function will be called from all functions in mock classes (default is set to false).
-h, --help Show help information.
Option 1: SPM
dependencies: [
.package(url: "https://github.com/uber/mockolo.git", from: "1.8.1"),
],
targets: [
.target(name: "MyTarget", dependencies: ["MockoloFramework"]),
]
Option 2: Cocoapods
target 'MyTarget' do
platform :osx, '10.14'
pod 'MockoloFramework', '~>1.1.2'
end
The install-script.sh
will build and package up the mockolo
binary and other necessary resources in the same bundle.
$ ./install-script.sh -h // see input options
$ ./install-script.sh -s [source dir] -t mockolo -d [destination dir] -o [output filename].tar.gz
This will create a tarball for distribution, which contains the mockolo
executable along with a necessary SwiftSyntax parser dylib (lib_InternalSwiftSyntaxParser.dylib). This allows running mockolo
without depending on where the dylib lives.
For example, Foo.swift contains:
/// @mockable
public protocol Foo {
var num: Int { get set }
func bar(arg: Float) -> String
}
Running ./mockolo -srcs Foo.swift -d ./OutputMocks.swift
will output:
public class FooMock: Foo {
init() {}
init(num: Int = 0) {
self.num = num
}
var numSetCallCount = 0
var underlyingNum: Int = 0
var num: Int {
get {
return underlyingNum
}
set {
underlyingNum = newValue
numSetCallCount += 1
}
}
var barCallCount = 0
var barHandler: ((Float) -> (String))?
func bar(arg: Float) -> String {
barCallCount += 1
if let barHandler = barHandler {
return barHandler(arg)
}
return ""
}
}
The above mock can now be used in a test as follows:
func testMock() {
let mock = FooMock(num: 5)
XCTAssertEqual(mock.numSetCallCount, 1)
mock.barHandler = { arg in
return String(arg)
}
XCTAssertEqual(mock.barCallCount, 1)
}
A list of override arguments can be passed in (delimited by a semicolon) to the annotation to set var types, typealiases, module names, etc.
/// @mockable(module: prefix = Bar)
public protocol Foo {
...
}
This will generate:
public class FooMock: Bar.Foo {
...
}
/// @mockable(typealias: T = AnyObject; U = StringProtocol)
public protocol Foo {
associatedtype T
associatedtype U: Collection where U.Element == T
associatedtype W
...
}
This will generate the following mock output:
public class FooMock: Foo {
typealias T = AnyObject // overriden
typealias U = StringProtocol // overriden
typealias W = Any // default placeholder type for typealias
...
}
For a var type such as an RxSwift observable:
/// @mockable(rx: intStream = ReplaySubject; doubleStream = BehaviorSubject)
public protocol Foo {
var intStream: Observable<Int> { get }
var doubleStream: Observable<Double> { get }
}
This will generate:
public class FooMock: Foo {
var intStreamSubject = ReplaySubject<Int>.create(bufferSize: 1)
var intStream: Observable<Int> { /* use intStreamSubject */ }
var doubleStreamSubject = BehaviorSubject<Int>(value: 0)
var doubleStream: Observable<Int> { /* use doubleStreamSubject */ }
}
To capture function arguments history:
/// @mockable(history: fooFunc = true)
public protocol Foo {
func fooFunc(val: Int)
func barFunc(_ val: (a: String, Float))
func bazFunc(val1: Int, val2: String)
}
This will generate:
public class FooMock: Foo {
var fooFuncCallCount = 0
var fooFuncArgValues = [Int]() // arguments captor
var fooFuncHandler: ((Int) -> ())?
func fooFunc(val: Int) {
fooFuncCallCount += 1
fooFuncArgValues.append(val) // capture arguments
if fooFuncHandler = fooFuncHandler {
fooFuncHandler(val)
}
}
...
var barFuncArgValues = [(a: String, Float)]() // tuple is also supported.
...
...
var bazFuncArgValues = [(Int, String)]()
...
}
and also, enable the arguments captor for all functions if you passed --enable-args-history
arg to mockolo
command.
NOTE: The arguments captor only supports singular types (e.g. variable, tuple). The closure variable is not supported.
To generate mocks for Combine's AnyPublisher:
/// @mockable(combine: fooPublisher = PassthroughSubject; barPublisher = CurrentValueSubject)
public protocol Foo {
var fooPublisher: AnyPublisher<String, Never> { get }
var barPublisher: AnyPublisher<Int, CustomError> { get }
}
This will generate:
public class FooMock: Foo {
public init() { }
public var fooPublisher: AnyPublisher<String, Never> { return self.fooPublisherSubject.eraseToAnyPublisher() }
public private(set) var fooPublisherSubject = PassthroughSubject<String, Never>()
public var barPublisher: AnyPublisher<Int, CustomError> { return self.barPublisherSubject.eraseToAnyPublisher() }
public private(set) var barPublisherSubject = CurrentValueSubject<Int, CustomError>(0)
}
You can also connect an AnyPublisher to a property within the protocol.
For example:
/// @mockable(combine: fooPublisher = @Published foo)
public protocol Foo {
var foo: String { get }
var fooPublisher: AnyPublisher<String, Never> { get }
}
This will generate:
public class FooMock: Foo {
public init() { }
public init(foo: String = "") {
self.foo = foo
}
public private(set) var fooSetCallCount = 0
@Published public var foo: String = "" { didSet { fooSetCallCount += 1 } }
public var fooPublisher: AnyPublisher<String, Never> { return self.$foo.setFailureType(to: Never.self).eraseToAnyPublisher() }
}
To override the generated mock name:
/// @mockable(override: name = FooMock)
public protocol FooProtocol { ... }
This will generate:
public class FooMock: FooProtocol { ... }
See CONTRIBUTING for more info.
If you run into any problems, please file a git issue. Please include:
swift --version
)git tag
to get a list of all the release versions or git log
to get a specific commit sha)Author: uber
Source Code: https://github.com/uber/mockolo
License: Apache-2.0 license
1677755640
docker run -d \
--restart=always \
-p 8080:8080 \
-p 8081:8081 \
--name smocker \
thiht/smocker
# This will be the deployment folder for the Smocker instance
mkdir -p /opt/smocker && cd /opt/smocker
wget -P /tmp https://github.com/Thiht/smocker/releases/latest/download/smocker.tar.gz
tar xf /tmp/smocker.tar.gz
nohup ./smocker -mock-server-listen-port=8080 -config-listen-port=8081 &
curl localhost:8081/version
Smocker exposes a configuration user interface. You can access it in your web browser on http://localhost:8081/.
Smocker exposes two ports:
8080
is the mock server port. It will expose the routes you register through the configuration port8081
is the configuration port. It's the port you will use to register new mocks. This port also exposes a user interface.To register a mock, you can use the YAML and the JSON formats. A basic mock might look like this:
# helloworld.yml
# This mock register two routes: GET /hello/world and GET /foo/bar
- request:
# Note: the method could be omitted because GET is the default
method: GET
path: /hello/world
response:
# Note: the status could be omitted because 200 is the default
status: 200
headers:
Content-Type: application/json
body: >
{
"hello": "Hello, World!"
}
- request:
method: GET
path: /foo/bar
response:
status: 204
You can then register it to the configuration server with the following command:
curl -XPOST \
--header "Content-Type: application/x-yaml" \
--data-binary "@helloworld.yml" \
localhost:8081/mocks
After your mock is registered, you can query the mock server on the specified route, so that it returns the expected response to you:
$ curl -i localhost:8080/hello/world
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 05 Sep 2019 15:49:32 GMT
Content-Length: 31
{
"hello": "Hello, World!"
}
To cleanup the mock server without restarting it, you can execute the following command:
curl -XPOST localhost:8081/reset
For more advanced usage, please read the project's documentation.
The backend is written in Go. You can use the following commands to manage the development lifecycle:
make start
: start the backend in development mode, with live reloadmake build
, make VERSION=xxx build
: compile the code and generate a binarymake lint
: run static analysis on the codemake format
: automatically format the backend codemake test
: execute unit testsmake test-integration
: execute integration testsThe frontend is written with TypeScript and React. You can use the following commands to manage the development lifecycle:
yarn install
: install the dependenciesyarn start
: start the frontend in development mode, with live reloadyarn build
: generate the transpiled and minified files and assetsyarn lint
: run static analysis on the codeyarn format
: automatically format the frontend codeyarn test
: execute unit testsyarn test:watch
: execute unit tests, with live reloadThe documentation is written in Markdown using Vuepress. You can use the following commands to manage the documentation:
yarn install
: install the dependenciesyarn docs:generate
: regenerate documentation screenshots (require the whole application to be started on the default ports)yarn docs:dev
: start the documentation in development mode, with live reloadyarn docs:build
: generate the static production documentationThe application can be packaged as a standalone Docker image. You can use the following commands to manage the development lifecycle:
make build-docker
, make VERSION=xxx build-docker
: build the application as a Docker imagemake start-docker
, make VERSION=xxx start-docker
: run a Smocker Docker imageIf you need to test Smocker with a base path, you can use the Caddyfile provided in the repository (Caddy v2):
make start-release
, make VERSION=xxx start-release
: create a released version of Smocker and launch it with /smocker/
as base pathmake start-caddy
: start Caddy to make Smocker accessible at http://localhost:8082/smocker/If you need to test Smocker with HTTPS enabled, the easiest way is to generate a locally signed certificate with mkcert:
# Install the local certificate authority
mkcert -install
# Create a certificate for localhost
mkcert -cert-file /tmp/cert.pem -key-file /tmp/key.pem localhost
Then, start Smocker with TLS enabled, using your generated certificate:
./smocker -mock-server-listen-port=44300 -config-listen-port=44301 -tls-enable -tls-cert-file=/tmp/cert.pem -tls-private-key-file=/tmp/key.pm
The documentation is available on smocker.dev.
Author: Thiht
Source Code: https://github.com/Thiht/smocker
License: MIT license
1677668905
Mocking library for TypeScript inspired by http://mockito.org/
mock
) (also abstract classes) #examplespy
) #examplewhen
) via:verify
)reset
, resetCalls
) #example, #examplecapture
) #example'Expected "convertNumberToString(strictEqual(3))" to be called 2 time(s). But has been called 1 time(s).'
)npm install ts-mockito --save-dev
// Creating mock
let mockedFoo:Foo = mock(Foo);
// Getting instance from mock
let foo:Foo = instance(mockedFoo);
// Using instance in source code
foo.getBar(3);
foo.getBar(5);
// Explicit, readable verification
verify(mockedFoo.getBar(3)).called();
verify(mockedFoo.getBar(anything())).called();
// Creating mock
let mockedFoo:Foo = mock(Foo);
// stub method before execution
when(mockedFoo.getBar(3)).thenReturn('three');
// Getting instance
let foo:Foo = instance(mockedFoo);
// prints three
console.log(foo.getBar(3));
// prints null, because "getBar(999)" was not stubbed
console.log(foo.getBar(999));
// Creating mock
let mockedFoo:Foo = mock(Foo);
// stub getter before execution
when(mockedFoo.sampleGetter).thenReturn('three');
// Getting instance
let foo:Foo = instance(mockedFoo);
// prints three
console.log(foo.sampleGetter);
Syntax is the same as with getter values.
Please note, that stubbing properties that don't have getters only works if Proxy object is available (ES6).
// Creating mock
let mockedFoo:Foo = mock(Foo);
// Getting instance
let foo:Foo = instance(mockedFoo);
// Some calls
foo.getBar(1);
foo.getBar(2);
foo.getBar(2);
foo.getBar(3);
// Call count verification
verify(mockedFoo.getBar(1)).once(); // was called with arg === 1 only once
verify(mockedFoo.getBar(2)).twice(); // was called with arg === 2 exactly two times
verify(mockedFoo.getBar(between(2, 3))).thrice(); // was called with arg between 2-3 exactly three times
verify(mockedFoo.getBar(anyNumber()).times(4); // was called with any number arg exactly four times
verify(mockedFoo.getBar(2)).atLeast(2); // was called with arg === 2 min two times
verify(mockedFoo.getBar(anything())).atMost(4); // was called with any argument max four times
verify(mockedFoo.getBar(4)).never(); // was never called with arg === 4
// Creating mock
let mockedFoo:Foo = mock(Foo);
let mockedBar:Bar = mock(Bar);
// Getting instance
let foo:Foo = instance(mockedFoo);
let bar:Bar = instance(mockedBar);
// Some calls
foo.getBar(1);
bar.getFoo(2);
// Call order verification
verify(mockedFoo.getBar(1)).calledBefore(mockedBar.getFoo(2)); // foo.getBar(1) has been called before bar.getFoo(2)
verify(mockedBar.getFoo(2)).calledAfter(mockedFoo.getBar(1)); // bar.getFoo(2) has been called before foo.getBar(1)
verify(mockedFoo.getBar(1)).calledBefore(mockedBar.getFoo(999999)); // throws error (mockedBar.getFoo(999999) has never been called)
let mockedFoo:Foo = mock(Foo);
when(mockedFoo.getBar(10)).thenThrow(new Error('fatal error'));
let foo:Foo = instance(mockedFoo);
try {
foo.getBar(10);
} catch (error:Error) {
console.log(error.message); // 'fatal error'
}
You can also stub method with your own implementation
let mockedFoo:Foo = mock(Foo);
let foo:Foo = instance(mockedFoo);
when(mockedFoo.sumTwoNumbers(anyNumber(), anyNumber())).thenCall((arg1:number, arg2:number) => {
return arg1 * arg2;
});
// prints '50' because we've changed sum method implementation to multiply!
console.log(foo.sumTwoNumbers(5, 10));
You can also stub method to resolve / reject promise
let mockedFoo:Foo = mock(Foo);
when(mockedFoo.fetchData("a")).thenResolve({id: "a", value: "Hello world"});
when(mockedFoo.fetchData("b")).thenReject(new Error("b does not exist"));
You can reset just mock call counter
// Creating mock
let mockedFoo:Foo = mock(Foo);
// Getting instance
let foo:Foo = instance(mockedFoo);
// Some calls
foo.getBar(1);
foo.getBar(1);
verify(mockedFoo.getBar(1)).twice(); // getBar with arg "1" has been called twice
// Reset mock
resetCalls(mockedFoo);
// Call count verification
verify(mockedFoo.getBar(1)).never(); // has never been called after reset
You can also reset calls of multiple mocks at once resetCalls(firstMock, secondMock, thirdMock)
Or reset mock call counter with all stubs
// Creating mock
let mockedFoo:Foo = mock(Foo);
when(mockedFoo.getBar(1)).thenReturn("one").
// Getting instance
let foo:Foo = instance(mockedFoo);
// Some calls
console.log(foo.getBar(1)); // "one" - as defined in stub
console.log(foo.getBar(1)); // "one" - as defined in stub
verify(mockedFoo.getBar(1)).twice(); // getBar with arg "1" has been called twice
// Reset mock
reset(mockedFoo);
// Call count verification
verify(mockedFoo.getBar(1)).never(); // has never been called after reset
console.log(foo.getBar(1)); // null - previously added stub has been removed
You can also reset multiple mocks at once reset(firstMock, secondMock, thirdMock)
let mockedFoo:Foo = mock(Foo);
let foo:Foo = instance(mockedFoo);
// Call method
foo.sumTwoNumbers(1, 2);
// Check first arg captor values
const [firstArg, secondArg] = capture(mockedFoo.sumTwoNumbers).last();
console.log(firstArg); // prints 1
console.log(secondArg); // prints 2
You can also get other calls using first()
, second()
, byCallIndex(3)
and more...
You can set multiple returning values for same matching values
const mockedFoo:Foo = mock(Foo);
when(mockedFoo.getBar(anyNumber())).thenReturn('one').thenReturn('two').thenReturn('three');
const foo:Foo = instance(mockedFoo);
console.log(foo.getBar(1)); // one
console.log(foo.getBar(1)); // two
console.log(foo.getBar(1)); // three
console.log(foo.getBar(1)); // three - last defined behavior will be repeated infinitely
Another example with specific values
let mockedFoo:Foo = mock(Foo);
when(mockedFoo.getBar(1)).thenReturn('one').thenReturn('another one');
when(mockedFoo.getBar(2)).thenReturn('two');
let foo:Foo = instance(mockedFoo);
console.log(foo.getBar(1)); // one
console.log(foo.getBar(2)); // two
console.log(foo.getBar(1)); // another one
console.log(foo.getBar(1)); // another one - this is last defined behavior for arg '1' so it will be repeated
console.log(foo.getBar(2)); // two
console.log(foo.getBar(2)); // two - this is last defined behavior for arg '2' so it will be repeated
Short notation:
const mockedFoo:Foo = mock(Foo);
// You can specify return values as multiple thenReturn args
when(mockedFoo.getBar(anyNumber())).thenReturn('one', 'two', 'three');
const foo:Foo = instance(mockedFoo);
console.log(foo.getBar(1)); // one
console.log(foo.getBar(1)); // two
console.log(foo.getBar(1)); // three
console.log(foo.getBar(1)); // three - last defined behavior will be repeated infinity
Possible errors:
const mockedFoo:Foo = mock(Foo);
// When multiple matchers, matches same result:
when(mockedFoo.getBar(anyNumber())).thenReturn('one');
when(mockedFoo.getBar(3)).thenReturn('one');
const foo:Foo = instance(mockedFoo);
foo.getBar(3); // MultipleMatchersMatchSameStubError will be thrown, two matchers match same method call
You can mock interfaces too, just instead of passing type to mock
function, set mock
function generic type Mocking interfaces requires Proxy
implementation
let mockedFoo:Foo = mock<FooInterface>(); // instead of mock(FooInterface)
const foo: SampleGeneric<FooInterface> = instance(mockedFoo);
You can mock abstract classes
const mockedFoo: SampleAbstractClass = mock(SampleAbstractClass);
const foo: SampleAbstractClass = instance(mockedFoo);
You can also mock generic classes, but note that generic type is just needed by mock type definition
const mockedFoo: SampleGeneric<SampleInterface> = mock(SampleGeneric);
const foo: SampleGeneric<SampleInterface> = instance(mockedFoo);
You can partially mock an existing instance:
const foo: Foo = new Foo();
const spiedFoo = spy(foo);
when(spiedFoo.getBar(3)).thenReturn('one');
console.log(foo.getBar(3)); // 'one'
console.log(foo.getBaz()); // call to a real method
You can spy on plain objects too:
const foo = { bar: () => 42 };
const spiedFoo = spy(foo);
foo.bar();
console.log(capture(spiedFoo.bar).last()); // [42]
Author: NagRock
Source Code: https://github.com/NagRock/ts-mockito
License: MIT license
1673405220
SwiftyMocky is a strongly typed framework for Mockito-like unit testing experience. Library depends on Sourcery, that scans your source code and generates Mocks Swift code for you!
The idea of SwiftyMocky is to automatically mock Swift protocols and protocol compositions. The main features are:
CLI was moved bask to the main (this) repo. CLI in this repository will be supported at least until version 5.0.0.
Current version has several significant changes. It removes deprecated methods (which might be breaking) and deprecates having CLI in the new repository.
SwiftyPrototype was also extracted to separate library. There are no more compilation flags, so if you were relying on SwiftyMocky with -DMockyCustom
, you will have to switch to SwiftyPrototype
.
We consider current version as stable. We are moving toward using the new Mockfile but the previous configuration format would be still supported. Library works with Swift 4.1, 4.2, 5.0, 5.1.2 and Sourcery 1.0.x.
While it is technically possible to integrate SwiftyMocky on Linux targets, there is no Mock generation feature there yet. You can use SwiftyMokcy runtime via SwiftPM though, as long as your are fine with generating mocks on mac machine.
The migration is not required, you can keep using SwiftyMocky as you did before. The Legacy setup is described in guides section.
Still, we would encourage to try new CLI and share a feedback. We believe it will make using and setting up SwiftyMocky way easier. If you have an existing setup, install CLI as per this guide and try:
> swiftymocky migrate
SwiftyMocky is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod "SwiftyMocky"
Use CLI tool from your project directory:
# To setup initial Mockfile
% ./Pods/SwiftyMocky/bin/swiftymocky setup
# To generate mocks
% ./Pods/SwiftyMocky/bin/swiftymocky generate
To install, add following to you Cartfile:
github "MakeAWishFoundation/SwiftyMocky"
Then execute carthage update
For Carthage, few additional steps are required ⚠️. For detailed install instructions, see full documentation or consult Carthage documentation.
You need to install CLI to generate mocks - see installation
Add SwiftyMocky to you Package.swift dependencies:
dependencies: [
.package(url: "https://github.com/MakeAWishFoundation/SwiftyMocky", from: "4.2.0"),
]
You need to install CLI to generate mocks - see installation
Note: Examples of SwiftyMocky integration as a tool for Unit tests, as well as a Prototyping framework, are here: https://github.com/MakeAWishFoundation/SM-Integration-Tests
> brew install mint
> mint install MakeAWishFoundation/SwiftyMocky
> marathon install MakeAWishFoundation/SwiftyMocky
Make:
Clone from https://github.com/MakeAWishFoundation/SwiftyMockyCLI and run make
in the root directory.
Annotate your protocols that are going to be mocked, making them adopt AutoMockable
protocol, or adding annotation comment above their definition in the source code.
Mocks are generated from your project root directory, based on configuration inside Mockfile.
> path/to/swiftymocky setup # if you don't have a Mockfile yet
> path/to/swiftymocky doctor # validate your setup
> path/to/swiftymocky generate # generate mocks
More informations about CLI and mock generation
If you don't want to migrate to our CLI and prefer to use "raw" Sourcery, please refer to this section in documentation.
Usage
Create 'dummy' protocol somewhere in your project, like: protocol AutoMockable { }
Adopt it by every protocol you want to actually mock.
protocol ToBeMocked: AutoMockable {
// ...
}
Alternatively, mark protocols that are meant to be mocked with sourcery annotation as following:
//sourcery: AutoMockable
protocol ToBeMocked {
// ...
}
Or use it to protocol compositions:
typealias ToBeMocked = OneProtocol & TwoProtocols & AutoMockable
Every protocol in source directories, having this annotation, will be added to Mock.generated.swift
All mocks has given method (accessible both as instance method or global function), with easy to use syntax, allowing to specify what should be return values for given methods (based on specified attributes).
All protocol methods are nicely put into Given, with matching signature. That allows to use auto-complete (just type .
) to see all mocked protocol methods, and specify return value for them.
All method attributes are wrapped as Parameter enum, allowing to choose between any
and value
, giving great flexibility to mock behaviour. Please consider following:
Given(mock, .surname(for name: .value("Johnny"), willReturn: "Bravo"))
Given(mock, .surname(for name: .any, willReturn: "Kowalsky"))
print(mock.surname(for: "Johny")) // Bravo
print(mock.surname(for: "Mathew")) // Kowalsky
print(mock.surname(for: "Joanna")) // Kowalsky
In verions 3.0 we introduced sequences and policies for better control of mock behvaiour.
Given(mock, .surname(for name: .any, willReturn: "Bravo", "Kowalsky", "Nguyen"))
print(mock.surname(for: "Johny")) // Bravo
print(mock.surname(for: "Johny")) // Kowalsky
print(mock.surname(for: "Johny")) // Nguyen
print(mock.surname(for: "Johny")) // and again Bravo
// ...
For more details please see full documentation.
All mocks has verify method (accessible both as instance method or global function), with easy to use syntax, allowing to verify, whether a method was called on mock, and how many times. It also provides convenient way to specify, whether method attributes matters (and which ones).
All protocol methods are nicely put into Verify, with matching signature. That allows to use auto-complete (just type .
) to see all mocked protocol methods, and specify which one we want to verify.
All method attributes are wrapped as Parameter enum, allowing to choose between any
, value
and matching
, giving great flexibility to tests. Please consider following:
// inject mock to sut. Every time sut saves user data, it should trigger storage storeUser method
sut.usersStorage = mockStorage
sut.saveUser(name: "Johny", surname: "Bravo")
sut.saveUser(name: "Johny", surname: "Cage")
sut.saveUser(name: "Jon", surname: "Snow")
// check if Jon Snow was stored at least one time
Verify(mockStorage, .storeUser(name: .value("Jon"), surname: .value("Snow")))
// storeUser method should be triggered 3 times in total, regardless of attributes values
Verify(mockStorage, 3, .storeUser(name: .any, surname: .any))
// storeUser method should be triggered 2 times with name Johny
Verify(mockStorage, 2, .storeUser(name: .value("Johny"), surname: .any))
// storeUser method should be triggered at least 2 times with name longer than 3
Verify(mockStorage, .moreOrEqual(to: 2), .storeUser(name: .matching({ $0.count > 3 }), surname: .any))
For Verify, you can use Count to specify how many times you expect something to be triggered. Count can be defined as explicit value, like 1
,2
,... or in more descriptive and flexible way, like .never
, more(than: 1)
, etc.
From SwiftyMocky 3.0, it is possible to use Given
and perform Verify
on properties as well, with respect to whether it is get or set:
mock.name = "Danny"
mock.name = "Joanna"
print(mock.name)
// Verify getter:
Verify(mock, 1, .name)
// Verify setter:
Verify(mock, 2, .name(set: .any))
Verify(mock, 1, .name(set: .value("Danny")))
Verify(mock, .never, .name(set: .value("Bishop")))
All mocks has perform method (accessible both as instance method or global function), with easy to use syntax, allowing to specify closure, that will be executed upon stubbed method being called.
It uses same parameter wrapping features as given, so you can specify different Perform cases for different attributes set.
It's very handy when working with completion block based approach.
Example:
// Perform allows to execute given closure, with all the method parameters, as soon as it is being called
Perform(mock, .methodThatTakesCompletionBlock(completion: .any, perform: { completion in
completion(true,nil)
}))
Documentation
Full documentation is available here, as well as through docs directory.
Guides - Table of contents
Changelog is available here
For list all supported features, check documentation here or guides
For more examples, check out our example project, or examples section in guides.
To run the example project, clone the repo, and run pod install
from the Example directory first.
To trigger mocks generation, run rake mock
or swiftymocky generate
from root directory (if you installed CLI).
Check out guides, or full documentation
Author: MakeAWishFoundation
Source Code: https://github.com/MakeAWishFoundation/SwiftyMocky
License: MIT license
1668056940
Reference implementation of the Stacks blockchain in Rust.
Stacks 2.0 is a layer-1 blockchain that connects to Bitcoin for security and enables decentralized apps and predictable smart contracts. Stacks 2.0 implements Proof of Transfer (PoX) mining that anchors to Bitcoin security. Leader election happens at the Bitcoin blockchain and Stacks (STX) miners write new blocks on the separate Stacks blockchain. With PoX there is no need to modify Bitcoin to enable smart contracts and apps around it. See this page for more details and resources.
Blockstack Topic/Tech | Where to learn more |
---|---|
Stacks 2.0 | master branch |
Stacks 1.0 | legacy branch |
Use the package | our core docs |
Develop a Blockstack App | our developer docs |
Use a Blockstack App | our browser docs |
Blockstack PBC the company | our website |
Normal releases in this repository that add features such as improved RPC endpoints, improved boot-up time, new event observer fields or event types, etc., are released on a monthly schedule. The currently staged changes for such releases are in the develop branch. It is generally safe to run a stacks-node
from that branch, though it has received less rigorous testing than release tags. If bugs are found in the develop
branch, please do report them as issues on this repository.
For fixes that impact the correct functioning or liveness of the network, hotfixes may be issued. These are patches to the main branch which are backported to the develop branch after merging. These hotfixes are categorized by priority according to the following rubric:
This repository uses a 5 part version number.
X.Y.Z.A.n
X = 2 and does not change in practice unless there’s another Stacks 2.0 type event
Y increments on consensus-breaking changes
Z increments on non-consensus-breaking changes that require a fresh chainstate (akin to semantic MAJOR)
A increments on non-consensus-breaking changes that do not require a fresh chainstate, but introduce new features (akin to semantic MINOR)
n increments on patches and hot-fixes (akin to semantic PATCH)
For example, a node operator running version 2.0.10.0.0
would not need to wipe and refresh their chainstate to upgrade to 2.0.10.1.0
or 2.0.10.0.1
. However, upgrading to 2.0.11.0.0
would require a new chainstate.
Stacks improvement proposals (SIPs) are aimed at describing the implementation of the Stacks blockchain, as well as proposing improvements. They should contain concise technical specifications of features or standards and the rationale behind it. SIPs are intended to be the primary medium for proposing new features, for collecting community input on a system-wide issue, and for documenting design decisions.
See SIP 000 for more details.
The SIPs are now located in the stacksgov/sips repository as part of the Stacks Community Governance organization.
Krypton is a Stacks 2 testnet with a fixed, two-minute block time, called regtest
. Regtest is generally unstable for regular use, and is reset often. See the regtest documentation for more information on using regtest.
Xenon is the Stacks 2 public testnet, which runs PoX against the Bitcoin testnet. It is the full implementation of the Stacks 2 blockchain, and should be considered a stable testnet for developing Clarity smart contracts. See the testnet documentation for more information on the public testnet.
Mainnet is the fully functional Stacks 2 blockchain, see the Stacks overview for information on running a Stacks node, mining, stacking, and writing Clarity smart contracts.
The first step is to ensure that you have Rust and the support software installed.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
For building on Windows, follow the rustup installer instructions at https://rustup.rs/
From there, you can clone this repository:
git clone --depth=1 https://github.com/blockstack/stacks-blockchain.git
cd stacks-blockchain
Then build the project:
cargo build
Run the tests:
cargo test testnet -- --test-threads=1
Here, we have generated a keypair that will be used for signing the upcoming transactions:
cargo run --bin blockstack-cli generate-sk --testnet
# Output
# {
# secretKey: "b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001",
# publicKey: "02781d2d3a545afdb7f6013a8241b9e400475397516a0d0f76863c6742210539b5",
# stacksAddress: "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH"
# }
This keypair is already registered in the testnet-follower-conf.toml
file, so it can be used as presented here.
We will interact with the following simple contract kv-store
. In our examples, we will assume this contract is saved to ./kv-store.clar
:
(define-map store { key: (string-ascii 32) } { value: (string-ascii 32) })
(define-public (get-value (key (string-ascii 32)))
(match (map-get? store { key: key })
entry (ok (get value entry))
(err 0)))
(define-public (set-value (key (string-ascii 32)) (value (string-ascii 32)))
(begin
(map-set store { key: key } { value: value })
(ok true)))
We want to publish this contract on chain, then issue some transactions that interact with it by setting some keys and getting some values, so we can observe read and writes.
Our first step is to generate and sign, using your private key, the transaction that will publish the contract kv-store
. To do that, we will use the subcommand:
cargo run --bin blockstack-cli publish --help
With the following arguments:
cargo run --bin blockstack-cli publish b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 515 0 kv-store ./kv-store.clar --testnet
The 515
is the transaction fee, denominated in microSTX. Right now, the testnet requires one microSTX per byte minimum, and this transaction should be less than 515 bytes. The third argument 0
is a nonce, that must be increased monotonically with each new transaction.
This command will output the binary format of the transaction. In our case, we want to pipe this output and dump it to a file that will be used later in this tutorial.
cargo run --bin blockstack-cli publish b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 515 0 kv-store ./kv-store.clar --testnet | xxd -r -p > tx1.bin
You can observe the state machine in action locally by running:
cargo stacks-node start --config=./testnet/stacks-node/conf/testnet-follower-conf.toml
testnet-follower-conf.toml
is a configuration file that you can use for setting genesis balances or configuring Event observers. You can grant an address an initial account balance by adding the following entries:
[[ustx_balance]]
address = "ST2VHM28V9E5QCRD6C73215KAPSBKQGPWTEE5CMQT"
amount = 100000000
The address
field is the Stacks testnet address, and the amount
field is the number of microSTX to grant to it in the genesis block. The addresses of the private keys used in the tutorial below are already added.
Assuming that the testnet is running, we can publish our kv-store
contract.
In another terminal (or file explorer), you can move the tx1.bin
generated earlier, to the mempool:
curl -X POST -H "Content-Type: application/octet-stream" --data-binary @./tx1.bin http://localhost:20443/v2/transactions
In the terminal window running the testnet, you can observe the state machine's reactions.
Now that our contract has been published on chain, let's try to submit some read / write transactions. We will start by trying to read the value associated with the key foo
.
To do that, we will use the subcommand:
cargo run --bin blockstack-cli contract-call --help
With the following arguments:
cargo run --bin blockstack-cli contract-call b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 500 1 ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH kv-store get-value -e \"foo\" --testnet | xxd -r -p > tx2.bin
contract-call
generates and signs a contract-call transaction.
We can submit the transaction by moving it to the mempool path:
curl -X POST -H "Content-Type: application/octet-stream" --data-binary @./tx2.bin http://localhost:20443/v2/transactions
Similarly, we can generate a transaction that would be setting the key foo
to the value bar
:
cargo run --bin blockstack-cli contract-call b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 500 2 ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH kv-store set-value -e \"foo\" -e \"bar\" --testnet | xxd -r -p > tx3.bin
And submit it by moving it to the mempool path:
curl -X POST -H "Content-Type: application/octet-stream" --data-binary @./tx3.bin http://localhost:20443/v2/transactions
Finally, we can issue a third transaction, reading the key foo
again, for ensuring that the previous transaction has successfully updated the state machine:
cargo run --bin blockstack-cli contract-call b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 500 3 ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH kv-store get-value -e \"foo\" --testnet | xxd -r -p > tx4.bin
And submit this last transaction by moving it to the mempool path:
curl -X POST -H "Content-Type: application/octet-stream" --data-binary @./tx4.bin http://localhost:20443/v2/transactions
Congratulations, you can now write your own smart contracts with Clarity.
Officially supported platforms: Linux 64-bit
, MacOS 64-bit
, Windows 64-bit
.
Platforms with second-tier status (builds are provided but not tested): MacOS Apple Silicon (ARM64)
, Linux ARMv7
, Linux ARM64
.
For help cross-compiling on memory-constrained devices, please see the community supported documentation here: Cross Compiling.
Beyond this Github project, Blockstack maintains a public forum and an opened Discord channel. In addition, the project maintains a mailing list which sends out community announcements.
The greater Blockstack community regularly hosts in-person meetups. The project's YouTube channel includes videos from some of these meetups, as well as video tutorials to help new users get started and help developers wrap their heads around the system's design.
You can learn more by visiting the Blockstack Website and checking out the documentation:
You can also read the technical papers:
If you have high-level questions about Blockstack, try searching our forum and start a new question if your question is not answered there.
PRs must include test coverage. However, if your PR includes large tests or tests which cannot run in parallel (which is the default operation of the cargo test
command), these tests should be decorated with #[ignore]
. If you add #[ignore]
tests, you should add your branch to the filters for the all_tests
job in our circle.yml (or if you are working on net code or marf code, your branch should be named such that it matches the existing filters there).
A test should be marked #[ignore]
if:
cargo test
in a vanilla environment (i.e., it does not need to run with --test-threads 1
).cargo test
execution (the cargo test
command will warn if this is not the case).This repository uses the default rustfmt formatting style. PRs will be checked against rustfmt
and will fail if not properly formatted.
You can check the formatting locally via:
cargo fmt --all -- --check
You can automatically reformat your commit via:
cargo fmt --all
Stacks tokens (STX) are mined by transferring BTC via PoX. To run as a miner, you should make sure to add the following config fields to your config file:
[node]
# Run as a miner
miner = True
# Bitcoin private key to spend
seed = "YOUR PRIVATE KEY"
# How long to wait for microblocks to arrive before mining a block to confirm them (in milliseconds)
wait_time_for_microblocks = 10000
# Run as a mock-miner, to test mining without spending BTC. Needs miner=True.
#mock_mining = True
[miner]
# Smallest allowed tx fee, in microSTX
min_tx_fee = 100
# Time to spend on the first attempt to make a block, in milliseconds.
# This can be small, so your node gets a block-commit into the Bitcoin mempool early.
first_attempt_time_ms = 1000
# Time to spend on subsequent attempts to make a block, in milliseconds.
# This can be bigger -- new block-commits will be RBF'ed.
subsequent_attempt_time_ms = 60000
# Time to spend mining a microblock, in milliseconds.
microblock_attempt_time_ms = 30000
You can verify that your node is operating as a miner by checking its log output to verify that it was able to find its Bitcoin UTXOs:
$ head -n 100 /path/to/your/node/logs | grep -i utxo
INFO [1630127492.031042] [testnet/stacks-node/src/run_loop/neon.rs:146] [main] Miner node: checking UTXOs at address: <redacted>
INFO [1630127492.062652] [testnet/stacks-node/src/run_loop/neon.rs:164] [main] UTXOs found - will run as a Miner node
Fee and cost estimators can be configured via the config section [fee_estimation]
:
[fee_estimation]
cost_estimator = naive_pessimistic
fee_estimator = fuzzed_weighted_median_fee_rate
fee_rate_fuzzer_fraction = 0.1
fee_rate_window_size = 5
cost_metric = proportion_dot_product
log_error = true
enabled = true
Fee and cost estimators observe transactions on the network and use the observed costs of those transactions to build estimates for viable fee rates and expected execution costs for transactions. Estimators and metrics can be selected using the configuration fields above, though the default values are the only options currently. log_error
controls whether or not the INFO logger will display information about the cost estimator accuracy as new costs are observed. Setting enabled = false
turns off the cost estimators. Cost estimators are not consensus-critical components, but rather can be used by miners to rank transactions in the mempool or client to determine appropriate fee rates for transactions before broadcasting them.
The fuzzed_weighted_median_fee_rate
uses a median estimate from a window of the fees paid in the last fee_rate_window_size
blocks. Estimates are then randomly "fuzzed" using uniform random fuzz of size up to fee_rate_fuzzer_fraction
of the base estimate.
For non-consensus breaking releases, this project uses the following release process:
The release must be timed so that it does not interfere with a prepare phase. The timing of the next Stacking cycle can be found here. A release to mainnet
should happen at least 24 hours before the start of a new cycle, to avoid interfering with the prepare phase. So, start by being aware of when the release can happen.
Before creating the release, the release manager must determine the version number for this release. The factors that determine the version number are discussed in Versioning. We assume, in this section, that the change is not consensus-breaking. So, the release manager must first determine whether there are any "non-consensus-breaking changes that require a fresh chainstate". This means, in other words, that the database schema has changed, but an automatic migration was not implemented. Then, the release manager should determine whether this is a feature release, as opposed to a hotfix or a patch. Given the answers to these questions, the version number can be computed.
The release manager enumerates the PRs or issues that would block the release. A label should be applied to each such issue/PR as 2.0.x.y.z-blocker
. The release manager should ping these issue/PR owners for updates on whether or not those issues/PRs have any blockers or are waiting on feedback.
The release manager should open a develop -> master
PR. This can be done before all the blocker PRs have merged, as it is helpful for the manager and others to see the staged changes.
The release manager must update the CHANGELOG.md
file with summaries what was Added
, Changed
, and Fixed
. The pull requests merged into develop
can be found here. Note, however, that GitHub apparently does not allow sorting by
merge time, so, when sorting by some proxy criterion, some care should be used to understand which PR's were merged after the last develop -> master
release PR. This CHANGELOG.md
should also be used as the description of the develop -> master
so that it acts as release notes when the branch is tagged.
Once the blocker PRs have merged, the release manager will create a new tag by manually triggering the stacks-blockchain
Github Actions workflow against the develop
branch, inputting the release candidate tag, 2.0.x.y.z-rc0
, in the Action's input textbox.
Once the release candidate has been built, and docker images, etc. are available, the release manager will notify various ecosystem participants to test the release candidate on various staging infrastructure:
The release manager will test that the release candidate successfully syncs with the current chain from genesis both in testnet and mainnet. This requires starting the release candidate with an empty chainstate and confirming that it synchronizes with the current chain tip.
If bugs or issues emerge from the rollout on staging infrastructure, the release will be delayed until those regressions are resolved. As regressions are resolved, additional release candidates should be tagged. The release manager is responsible for updating the develop -> master
PR with information about the discovered issues, even if other community members and developers may be addressing the discovered issues.
Once the final release candidate has rolled out successfully without issue on the above staging infrastructure, the release manager tags 2 additional stacks-blockchain
team members to review the develop -> master
PR. If there is a merge conflict in this PR, this is the protocol: open a branch off of develop, merge master into that branch, and then open a PR from this side branch to develop. The merge conflicts will be resolved.
Once reviewed and approved, the release manager merges the PR, and tags the release via the stacks-blockchain
Github action by clicking "Run workflow" and providing the release version as the tag (e.g., 2.0.11.1.0
) This creates a release and release images. Once the release has been created, the release manager should update the Github release text with the CHANGELOG.md
"top-matter" for the release.
Author: Stacks-network
Source Code: https://github.com/stacks-network/stacks-blockchain
License: GPL-3.0 license
1666132800
OHHTTPStubs
is a library designed to stub your network requests very easily. It can help you:
It works with NSURLConnection
, NSURLSession
, AFNetworking
, Alamofire
or any networking framework that use Cocoa's URL Loading System.
Documentation & Usage Examples
OHHTTPStubs
headers are fully documented using Appledoc-like / Headerdoc-like comments in the header files. You can also read the online documentation here.
In Objective-C
[HTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
return [request.URL.host isEqualToString:@"mywebservice.com"];
} withStubResponse:^HTTPStubsResponse*(NSURLRequest *request) {
// Stub it with our "wsresponse.json" stub file (which is in same bundle as self)
NSString* fixture = OHPathForFile(@"wsresponse.json", self.class);
return [HTTPStubsResponse responseWithFileAtPath:fixture
statusCode:200 headers:@{@"Content-Type":@"application/json"}];
}];
In Swift
This example is using the Swift helpers found in OHHTTPStubsSwift.swift
provided by the OHHTTPStubs/Swift
subspec or OHHTTPStubs
package.
stub(condition: isHost("mywebservice.com")) { _ in
// Stub it with our "wsresponse.json" stub file (which is in same bundle as self)
let stubPath = OHPathForFile("wsresponse.json", type(of: self))
return fixture(filePath: stubPath!, headers: ["Content-Type":"application/json"])
}
Note: if you're using OHHTTPStubs
's Swiftier API (OHHTTPStubsSwift.swift
and the Swift
subspec or OHTTPStubsSwift
package), you can also compose the matcher functions like this: stub(isScheme("http") && isHost("myhost")) { … }
OHHTTPStubs
.Instead of writing the content of the stubs you want to use manually, you can use tools like SWHttpTrafficRecorder to record network requests into files. This way you can later use those files as stub responses.
This tool can record all three formats that are supported by OHHTTPStubs
(the HTTPMessage
format, the simple response boby/content file, and the Mocktail
format).
(There are also other ways to perform a similar task, including using curl -is <url> >foo.response
to generate files compatible with the HTTPMessage
format, or using other network recording libraries similar to SWHttpTrafficRecorder
).
Compatibility
OHHTTPStubs
is compatible with iOS5+, OS X 10.7+, tvOS.OHHTTPStubs
also works with NSURLSession
as well as any network library wrapping them.OHHTTPStubs
is fully compatible with Swift 3.x, 4.x and Swift 5.x.Nullability annotations have also been added to the ObjC API to allow a cleaner API when used from Swift even if you don't use the dedicated Swift API wrapper provided by OHHTTPStubsSwift.swift
.
Updating to Version 9.0+
OH
prefix (OHHHTTPStubs
-> HTTPStubs
, OHHTTPStubsResponse
-> HTTPStubsResponse
, etc).OHPathHelpers
class was renamed HTTPStubsPathHelpers
.Installing in your projects
Using CocoaPods is the recommended way.
OHHTTPStubs
from Objective-C only, add pod 'OHHTTPStubs'
to your Podfile
.OHHTTPStubs
from Swift, add pod 'OHHTTPStubs/Swift'
to your Podfile
instead.pod 'OHHTTPStubs/Swift' # includes the Default subspec, with support for NSURLSession & JSON, and the Swiftier API wrappers
OHHTTPStubs
is split into subspecs so that when using Cocoapods, you can get only what you need, no more, no less.
NSURLSession
, JSON
, and OHPathHelpers
Swift
subspec adds the Swiftier API to that default subspecHTTPMessage
and Mocktail
are opt-in subspecs: list them explicitly if you need themOHPathHelpers
doesn't depend on Core
and can be used independently of OHHTTPStubs
altogetherList of all the subspecs & their dependencies
Here's a list of which subspecs are included for each of the different lines you could use in your Podfile
:
Subspec | Core | NSURLSession | JSON | Swift | OHPathHelpers | HTTPMessage | Mocktail |
---|---|---|---|---|---|---|---|
pod 'OHHTTPStubs' | ✅ | ✅ | ✅ | ✅ | |||
pod 'OHHTTPStubs/Default' | ✅ | ✅ | ✅ | ✅ | |||
pod 'OHHTTPStubs/Swift' | ✅ | ✅ | ✅ | ✅ | ✅ | ||
pod 'OHHTTPStubs/Core' | ✅ | ||||||
pod 'OHHTTPStubs/NSURLSession' | ✅ | ✅ | |||||
pod 'OHHTTPStubs/JSON' | ✅ | ✅ | |||||
pod 'OHHTTPStubs/OHPathHelpers' | ✅ | ||||||
pod 'OHHTTPStubs/HTTPMessage' | ✅ | ✅ | |||||
pod 'OHHTTPStubs/Mocktail' | ✅ | ✅ |
OHHTTPStubs
is compatible with Swift Package Manager, and provides 2 targets for consumption: OHHTTPStubs
and OHHTTPStubsSwift
.
OHHTTPStubs
is equivalent to the OHHTTPStubs
subspec.OHHTTPStubsSwift
is equivalent to the OHHTTPStubs/Swift
subspec.Note: We currently do not have support for the HTTPMessage or Mocktail subspecs in Swift Package Manager. If you are interested in these, please open an issue to explain your needs.
OHHTTPStubs
is also compatible with Carthage. Just add it to your Cartfile
.
Note: The OHHTTPStubs.framework
built with Carthage will include all features of OHHTTPStubs
turned on (in other words, all subspecs of the pod), including NSURLSession
and JSON
support, OHPathHelpers
, HTTPMessage
and Mocktail
support, and the Swiftier API.
OHHTTPStubs
supports Swift 3.0 (Xcode 8+), Swift 3.1 (Xcode 8.3+), Swift 3.2 (Xcode 9.0+), Swift 4.0 (Xcode 9.0+), Swift 4.1 (Xcode 9.3+), Swift 4.2 (Xcode 10+), Swift 5.0 (Xcode 10.2), and Swift 5.1 (Xcode 11) however we are only testing Swift 4.x (using Xcode 9.1 and 10.1) and Swift 5.x (using Xcode 10.2 AND 11) in CI.
Here are some details about the correct setup you need depending on how you integrated OHHTTPStubs
into your project.
CocoaPods: nothing to do
If you use CocoaPods version 1.1.0.beta.1
or later, then CocoaPods will compile OHHTTPStubs
with the right Swift Version matching the one you use for your project automatically. You have nothing to do! 🎉
For more info, see CocoaPods/CocoaPods#5540 and CocoaPods/CocoaPods#5760.
Carthage: choose the right version
The project is set up with SWIFT_VERSION=5.0
on master
.
This means that the framework on master
will build using:
If you want Carthage to build the framework with Swift 3.x you can:
OHHTTPStubs
(6.2.0) — whose master
branch uses 3.0
SWIFT_VERSION
build setting to 3.0
SWIFT_VERSION
to carthage via XCODE_XCCONFIG_FILE=<config file declaring SWIFT_VERSION> carthage build
Special Considerations
OHHTTPStubs
is ideal to write unit tests that normally would perform network requests. But if you use it in your unit tests, don't forget to:
[HTTPStubs removeAllStubs]
in your tearDown
method. see this wiki page for more infoOHHTTPStubs
is automatically loaded and installed (at the time the library is loaded in memory), both for:
NSURLConnection
or [NSURLSession sharedSession]
— thanks to this codeNSURLSession
that was created via [NSURLSession sessionWithConfiguration:…]
and using either [NSURLSessionConfiguration defaultSessionConfiguration]
or [NSURLSessionConfiguration ephemeralSessionConfiguration]
configuration — thanks to method swizzling done here in the code.If you need to disable (and re-enable) OHHTTPStubs
— globally or per NSURLSession
— you can use [HTTPStubs setEnabled:]
/ [HTTPStubs setEnabled:forSessionConfiguration:]
.
OHHTTPStubs
can't work on background sessions (sessions created using [NSURLSessionConfiguration backgroundSessionConfiguration]
) because background sessions don't allow the use of custom NSURLProtocols
and are handled by the iOS Operating System itself.OHHTTPStubs
don't simulate data upload. The NSURLProtocolClient
@protocol
does not provide a way to signal the delegate that data has been sent (only that some has been loaded), so any data in the HTTPBody
or HTTPBodyStream
of an NSURLRequest
, or data provided to -[NSURLSession uploadTaskWithRequest:fromData:];
will be ignored, and more importantly, the -URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
delegate method will never be called when you stub the request using OHHTTPStubs
.OHTTPStubs
has a known issue with redirects that we believe is an Apple bug. It has been discussed here and here. The actual result of this bug is that redirects with a zero second delay may nondeterministically end up with a null response.As far as I know, there's nothing we can do about those three limitations. Please let me know if you know a solution that would make that possible anyway.
OHHTTPStubs
can be used on apps submitted on the App Store. It does not use any private API and nothing prevents you from shipping it.
But you generally only use stubs during the development phase and want to remove your stubs when submitting to the App Store. So be careful to only include OHHTTPStubs
when needed (only in your test targets, or only inside #if DEBUG
sections, or by using per-Build-Configuration pods) to avoid forgetting to remove it when the time comes that you release for the App Store and you want your requests to hit the real network!
License and Credits
This project and library has been created by Olivier Halligon (@aligatr on Twitter) and is under the MIT License.
It has been inspired by this article from InfiniteLoop.dk.
I would also like to thank:
NSInputStream
If you want to support the development of this library, feel free to . Thanks to all contributors so far!
Author: AliSoftware
Source Code: https://github.com/AliSoftware/OHHTTPStubs
License: MIT license
1665065160
Provide mock test for redis query, Compatible with github.com/go-redis/redis/v8
Confirm that you are using redis.Client the version is github.com/go-redis/redis/v8
go get github.com/go-redis/redismock/v8
RedisClient
var ctx = context.TODO()
func NewsInfoForCache(redisDB *redis.Client, newsID int) (info string, err error) {
cacheKey := fmt.Sprintf("news_redis_cache_%d", newsID)
info, err = redisDB.Get(ctx, cacheKey).Result()
if err == redis.Nil {
// info, err = call api()
info = "test"
err = redisDB.Set(ctx, cacheKey, info, 30 * time.Minute).Err()
}
return
}
func TestNewsInfoForCache(t *testing.T) {
db, mock := redismock.NewClientMock()
newsID := 123456789
key := fmt.Sprintf("news_redis_cache_%d", newsID)
// mock ignoring `call api()`
mock.ExpectGet(key).RedisNil()
mock.Regexp().ExpectSet(key, `[a-z]+`, 30 * time.Minute).SetErr(errors.New("FAIL"))
_, err := NewsInfoForCache(db, newsID)
if err == nil || err.Error() != "FAIL" {
t.Error("wrong error")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Error(err)
}
}
RedisCluster
clusterClient, clusterMock := redismock.NewClusterMock()
RedisClient:
Subscribe
/ PSubscribe
RedisCluster
Subscribe
/ PSubscribe
Pipeline
/ TxPipeline
Watch
DBSize
Author: Go-redis
Source Code: https://github.com/go-redis/redismock
License: BSD-2-Clause license